Implemented type checks

This commit is contained in:
Eduard Urbach 2024-08-07 16:20:03 +02:00
parent 6e848774ed
commit 1b13539b22
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
19 changed files with 199 additions and 79 deletions

View File

@ -14,7 +14,7 @@ close(fd Int) -> Int {
return syscall(3, fd) 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) return syscall(9, address, length, protection, flags)
} }

View File

@ -23,8 +23,9 @@ const (
var ( var (
AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} 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} 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, RBP, R11, R12, R13, R14, R15}
CallRegisters = SyscallRegisters InputRegisters = SyscallInputRegisters
ReturnValueRegisters = SyscallRegisters OutputRegisters = SyscallInputRegisters
) )

View File

@ -32,13 +32,13 @@ func (r *Result) finalize() ([]byte, []byte) {
} }
final.Call("main.main") final.Call("main.main")
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit)
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0)
final.Syscall() final.Syscall()
final.Label(asm.LABEL, "_crash") final.Label(asm.LABEL, "_crash")
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit)
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 1) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1)
final.Syscall() final.Syscall()
// This will place the main function immediately after the entry point // This will place the main function immediately after the entry point

View File

@ -15,32 +15,33 @@ import (
func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
var ( var (
pkg = f.Package pkg = f.Package
nameRoot = root.Children[0] nameNode = root.Children[0]
fn *Function fn *Function
name string name string
fullName string fullName string
exists bool exists bool
) )
if nameRoot.IsLeaf() { if nameNode.IsLeaf() {
name = nameRoot.Token.Text(f.File.Bytes) name = nameNode.Token.Text(f.File.Bytes)
if name == "syscall" {
return nil, f.CompileSyscall(root)
}
} else { } else {
pkg = nameRoot.Children[0].Token.Text(f.File.Bytes) pkg = nameNode.Children[0].Token.Text(f.File.Bytes)
name = nameRoot.Children[1].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 pkg != f.File.Package {
if f.File.Imports == nil { if f.File.Imports == nil {
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position)
} }
imp, exists := f.File.Imports[pkg] imp, exists := f.File.Imports[pkg]
if !exists { if !exists {
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position)
} }
imp.Used = true imp.Used = true
@ -54,41 +55,40 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
fn, exists = f.Functions[fullName] fn, exists = f.Functions[fullName]
if !exists { if !exists {
return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position)
}
} }
parameters := root.Children[1:] parameters := root.Children[1:]
registers := f.CPU.Input[:len(parameters)] registers := f.CPU.Input[:len(parameters)]
if isSyscall { for i := len(parameters) - 1; i >= 0; i-- {
registers = f.CPU.Syscall[:len(parameters)] typ, err := f.ExpressionToRegister(parameters[i], registers[i])
}
err := f.ExpressionsToRegisters(parameters, registers)
if err != nil { if err != nil {
return fn, err return nil, err
} }
// TODO: Save all return value registers of the function if typ != fn.Parameters[i].Type {
f.SaveRegister(f.CPU.Output[0]) 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)
}
}
for _, register := range f.CPU.Output[:len(fn.ReturnTypes)] {
f.SaveRegister(register)
}
// Push
for _, register := range f.CPU.General { for _, register := range f.CPU.General {
if f.RegisterIsUsed(register) { if f.RegisterIsUsed(register) {
f.Register(asm.PUSH, 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 { for _, register := range registers {
if register == f.CPU.Output[0] && root.Parent != nil { if register == f.CPU.Output[0] && root.Parent != nil {
continue continue
@ -97,7 +97,6 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
f.FreeRegister(register) f.FreeRegister(register)
} }
// Pop
for i := len(f.CPU.General) - 1; i >= 0; i-- { for i := len(f.CPU.General) - 1; i >= 0; i-- {
register := f.CPU.General[i] register := f.CPU.General[i]

View File

@ -41,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
} }
count := 0 count := 0
_, err := f.CompileCall(right) called, err := f.CompileCall(right)
if err != nil { if err != nil {
return err return err
@ -54,6 +54,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
return err return err
} }
if called != nil {
variable.Type = called.ReturnTypes[count]
}
f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count])
f.AddVariable(variable) f.AddVariable(variable)
count++ count++

View File

@ -2,6 +2,8 @@ package core
import ( import (
"git.akyoto.dev/cli/q/src/build/ast" "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. // CompileReturn compiles a return instruction.
@ -12,5 +14,22 @@ func (f *Function) CompileReturn(node *ast.Return) error {
return nil 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
} }

View File

@ -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
}

View File

@ -31,7 +31,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
} }
if fn == nil || len(fn.ReturnTypes) == 0 { if fn == nil || len(fn.ReturnTypes) == 0 {
return types.Invalid, err return types.Any, err
} }
return fn.ReturnTypes[0], err return fn.ReturnTypes[0], err
@ -72,6 +72,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
return types.Invalid, err 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) err = f.Execute(node.Token, register, right)
if register != final { if register != final {

View File

@ -27,10 +27,11 @@ func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Fu
}, },
CPU: cpu.CPU{ CPU: cpu.CPU{
All: x64.AllRegisters, All: x64.AllRegisters,
Input: x64.CallRegisters,
General: x64.GeneralRegisters, General: x64.GeneralRegisters,
Syscall: x64.SyscallRegisters, Input: x64.InputRegisters,
Output: x64.ReturnValueRegisters, Output: x64.OutputRegisters,
SyscallInput: x64.SyscallInputRegisters,
SyscallOutput: x64.SyscallOutputRegisters,
}, },
}, },
} }

View File

@ -42,7 +42,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.
label := f.AddBytes(data) label := f.AddBytes(data)
f.SaveRegister(register) f.SaveRegister(register)
f.RegisterLabel(asm.MOVE, register, label) f.RegisterLabel(asm.MOVE, register, label)
return types.Int, nil return types.Pointer, nil
default: default:
return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position) return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position)

View File

@ -4,7 +4,8 @@ package cpu
type CPU struct { type CPU struct {
All []Register All []Register
General []Register General []Register
Syscall []Register
Input []Register Input []Register
Output []Register Output []Register
SyscallInput []Register
SyscallOutput []Register
} }

View File

@ -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)
}

View File

@ -229,7 +229,12 @@ func (s *Scanner) scanFile(path string, pkg string) error {
function := core.NewFunction(pkg, name, file, body) function := core.NewFunction(pkg, name, file, body)
if typeStart != -1 { 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] parameters := tokens[paramsStart:paramsEnd]
@ -241,8 +246,8 @@ func (s *Scanner) scanFile(path string, pkg string) error {
} }
name := tokens[0].Text(contents) name := tokens[0].Text(contents)
dataType := types.New(tokens[1:]) dataType := types.New(tokens[1:].Text(contents))
register := x64.CallRegisters[count] register := x64.InputRegisters[count]
uses := token.Count(function.Body, contents, token.Identifier, name) uses := token.Count(function.Body, contents, token.Identifier, name)
if uses == 0 { if uses == 0 {

View File

@ -47,6 +47,7 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope {
Name: v.Name, Name: v.Name,
Register: v.Register, Register: v.Register,
Alive: count, Alive: count,
Type: v.Type,
}) })
} }
} }

View File

@ -1,8 +1,6 @@
package types package types
import "git.akyoto.dev/cli/q/src/build/token"
// New creates a new type from a list of tokens. // New creates a new type from a list of tokens.
func New(tokens token.List) Type { func New(name string) Type {
return Int return Type(name)
} }

View File

@ -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
}

View File

@ -1,8 +1,10 @@
package types package types
type Type int type Type string
const ( const (
Invalid Type = iota // Invalid is an invalid type. Invalid = ""
Int Any = "Any"
Int = "Int"
Pointer = "Pointer"
) )

View File

@ -0,0 +1,7 @@
main() {
writeToMemory(0)
}
writeToMemory(p Pointer) {
p[0] = 'A'
}

View File

@ -36,6 +36,7 @@ var errs = []struct {
{"MissingOperand.q", errors.MissingOperand}, {"MissingOperand.q", errors.MissingOperand},
{"MissingOperand2.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand},
{"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}},
{"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int", ParameterName: "p"}},
{"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}},
{"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}},
{"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}},