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

View File

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

View File

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

View File

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

View File

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

View File

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

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 {
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 {

View File

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

View File

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

View File

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

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)
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 {

View File

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

View File

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

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
type Type int
type Type string
const (
Invalid Type = iota // Invalid is an invalid type.
Int
Invalid = ""
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},
{"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"}},