From 1b13539b22d6e73574b110fab2acdfbe3b520921 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 7 Aug 2024 16:20:03 +0200 Subject: [PATCH] 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"}},