Initial work on fixing the hellish nightmare of bugs of this repo

This commit is contained in:
Mattia Giambirtone 2023-06-30 17:26:42 +02:00
parent 919cc25579
commit 20a2f07eba
Signed by: nocturn9x
GPG Key ID: 8270F9F467971E59
6 changed files with 113 additions and 85 deletions

View File

@ -25,13 +25,13 @@ when not defined(gcArc) and not defined(gcOrc):
import std/math
import std/strformat
import std/segfaults
import std/strutils
import std/sets
import std/monotimes
when debugVM or debugMem or debugGC or debugAlloc:
import std/strformat
import std/sequtils
import std/terminal
@ -1065,8 +1065,12 @@ proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[], repl:
self.lastDebugCommand = ""
try:
self.dispatch()
except Defect as e:
stderr.writeLine(&"Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
except CatchableError as e:
stderr.writeLine(&"Fatal error at bytecode offset {self.ip - 1}: {e.name} -> {e.msg}")
except NilAccessDefect:
stderr.writeLine("Memory Access Violation: SIGSEGV")
stderr.writeLine(&"Memory Access Violation (bytecode offset {self.ip}): SIGSEGV")
quit(1)
if not repl:
# We clean up after ourselves!

View File

@ -64,6 +64,7 @@ type
isBuiltin*: bool
case kind*: TypeKind:
of Function:
nameObj*: Name
isLambda*: bool
isGenerator*: bool
isCoroutine*: bool
@ -99,7 +100,7 @@ type
## A name enumeration type
None, Module, Argument, Var, Function, CustomType, Enum
Name* = ref object of RootObj
Name* = ref object
## A generic name object
# Type of the identifier (NOT of the value!)
@ -345,6 +346,22 @@ proc step*(self: Compiler): ASTNode {.inline.} =
self.current += 1
proc wrap*(self: Type): Type =
## Wraps a type in a typevar if it's not already
## wrapped
if self.kind != Typevar:
return Type(kind: Typevar, wrapped: self)
return self
proc unwrap*(self: Type): Type =
## Unwraps a typevar if it's not already
## unwrapped
if self.kind == Typevar:
return self.wrapped
return self
# Peon's type inference and name resolution system is very flexible
# and can be reused across multiple compilation backends
@ -420,25 +437,27 @@ proc compareUnions*(self: Compiler, a, b: seq[tuple[match: bool, kind: Type]]):
proc compare*(self: Compiler, a, b: Type): bool =
## Compares two type objects
## for equality
result = false
# Note: 'All' is a type internal to the peon
# compiler that cannot be generated from user
# code in any way. It's used mostly for matching
# function return types (at least until we don't
# have return type inference) and it matches any
# type, including nil (nim's nil, not our nil)
# type, including nil (nim's nil, aka no type at all,
# not peon's nil which still has a type)
if a.isNil():
return b.isNil() or b.kind == All
elif b.isNil():
if b.isNil():
return a.isNil() or a.kind == All
elif a.kind == All or b.kind == All:
if a.kind == All or b.kind == All:
return true
if a.kind == b.kind:
# Here we compare types with the same kind discriminant
case a.kind:
of Int8, UInt8, Int16, UInt16, Int32,
UInt32, Int64, UInt64, Float32, Float64,
Char, Byte, String, Nil, TypeKind.Nan, Bool, TypeKind.Inf, Any:
Char, Byte, String, Nil, TypeKind.Nan, Bool,
TypeKind.Inf, Any, Auto:
return true
of Typevar:
return self.compare(a.wrapped, b.wrapped)
@ -481,17 +500,21 @@ proc compare*(self: Compiler, a, b: Type): bool =
return true
else:
discard # TODO: Custom types, enums
elif a.kind == Union:
if a.kind == Typevar:
return self.compare(a.wrapped, b)
if b.kind == Typevar:
return self.compare(a, b.wrapped)
if a.kind == Union:
for constraint in a.types:
if self.compare(constraint.kind, b) and constraint.match:
return true
return false
elif b.kind == Union:
if b.kind == Union:
for constraint in b.types:
if self.compare(constraint.kind, a) and constraint.match:
return true
return false
elif a.kind == Generic:
if a.kind == Generic:
if a.asUnion:
for constraint in a.cond:
if self.compare(constraint.kind, b) and constraint.match:
@ -502,7 +525,7 @@ proc compare*(self: Compiler, a, b: Type): bool =
if not self.compare(constraint.kind, b) or not constraint.match:
return false
return true
elif b.kind == Generic:
if b.kind == Generic:
if b.asUnion:
for constraint in b.cond:
if self.compare(constraint.kind, a) and constraint.match:
@ -513,7 +536,7 @@ proc compare*(self: Compiler, a, b: Type): bool =
if not self.compare(constraint.kind, a) or not constraint.match:
return false
return true
elif a.kind == Any or b.kind == Any:
if a.kind == Any or b.kind == Any:
# Here we already know that neither of
# these types are nil, so we can always
# just return true
@ -526,47 +549,47 @@ proc toIntrinsic*(name: string): Type =
## type if it is valid and returns nil
## otherwise
if name == "any":
return Type(kind: Any)
return Type(kind: Any, isBuiltin: true)
elif name == "all":
return Type(kind: All)
return Type(kind: All, isBuiltin: true)
elif name == "auto":
return Type(kind: Auto)
return Type(kind: Auto, isBuiltin: true)
elif name in ["int", "int64", "i64"]:
return Type(kind: Int64)
return Type(kind: Int64, isBuiltin: true)
elif name in ["uint64", "u64", "uint"]:
return Type(kind: UInt64)
return Type(kind: UInt64, isBuiltin: true)
elif name in ["int32", "i32"]:
return Type(kind: Int32)
return Type(kind: Int32, isBuiltin: true)
elif name in ["uint32", "u32"]:
return Type(kind: UInt32)
return Type(kind: UInt32, isBuiltin: true)
elif name in ["int16", "i16", "short"]:
return Type(kind: Int16)
return Type(kind: Int16, isBuiltin: true)
elif name in ["uint16", "u16"]:
return Type(kind: UInt16)
return Type(kind: UInt16, isBuiltin: true)
elif name in ["int8", "i8"]:
return Type(kind: Int8)
return Type(kind: Int8, isBuiltin: true)
elif name in ["uint8", "u8"]:
return Type(kind: UInt8)
return Type(kind: UInt8, isBuiltin: true)
elif name in ["f64", "float", "float64"]:
return Type(kind: Float64)
return Type(kind: Float64, isBuiltin: true)
elif name in ["f32", "float32"]:
return Type(kind: Float32)
return Type(kind: Float32, isBuiltin: true)
elif name in ["byte", "b"]:
return Type(kind: Byte)
return Type(kind: Byte, isBuiltin: true)
elif name in ["char", "c"]:
return Type(kind: Char)
return Type(kind: Char, isBuiltin: true)
elif name == "nan":
return Type(kind: TypeKind.Nan)
return Type(kind: TypeKind.Nan, isBuiltin: true)
elif name == "nil":
return Type(kind: Nil)
return Type(kind: Nil, isBuiltin: true)
elif name == "inf":
return Type(kind: TypeKind.Inf)
return Type(kind: TypeKind.Inf, isBuiltin: true)
elif name == "bool":
return Type(kind: Bool)
return Type(kind: Bool, isBuiltin: true)
elif name == "typevar":
return Type(kind: Typevar)
return Type(kind: Typevar, isBuiltin: true)
elif name == "string":
return Type(kind: String)
return Type(kind: String, isBuiltin: true)
proc infer*(self: Compiler, node: LiteralExpr): Type =
@ -577,7 +600,7 @@ proc infer*(self: Compiler, node: LiteralExpr): Type =
of intExpr, binExpr, octExpr, hexExpr:
let size = node.token.lexeme.split("'")
if size.len() == 1:
return Type(kind: Int64)
return Type(kind: Int64, isBuiltin: true)
let typ = size[1].toIntrinsic()
if not self.compare(typ, nil):
return typ
@ -586,18 +609,18 @@ proc infer*(self: Compiler, node: LiteralExpr): Type =
of floatExpr:
let size = node.token.lexeme.split("'")
if size.len() == 1:
return Type(kind: Float64)
return Type(kind: Float64, isBuiltin: true)
let typ = size[1].toIntrinsic()
if not typ.isNil():
return typ
else:
self.error(&"invalid type specifier '{size[1]}' for float", node)
of trueExpr:
return Type(kind: Bool)
return Type(kind: Bool, isBuiltin: true)
of falseExpr:
return Type(kind: Bool)
return Type(kind: Bool, isBuiltin: true)
of strExpr:
return Type(kind: String)
return Type(kind: String, isBuiltin: true)
else:
discard # Unreachable
@ -847,6 +870,10 @@ proc match*(self: Compiler, name: string, kind: Type, node: ASTNode = nil, allow
self.error(&"expecting an implementation for function '{impl[0].ident.token.lexeme}' declared in module '{impl[0].owner.ident.token.lexeme}' at line {impl[0].ident.token.line} of type '{self.stringify(impl[0].valueType)}'")
result = impl[0]
result.resolved = true
if result.kind == NameKind.Var:
# We found a function bound to a variable,
# so we return the original function's name object
result = result.valueType.nameObj
for (a, b) in zip(result.valueType.args, kind.args):
if not a.kind.isAny() and b.kind.isAny():
self.error("any is not a valid type in this context", node)
@ -947,6 +974,7 @@ proc declare*(self: Compiler, node: ASTNode): Name {.discardable.} =
if node.generics.len() > 0:
fn.isGeneric = true
self.names.add(fn)
fn.valueType.nameObj = fn
self.prepareFunction(fn)
n = fn
of NodeKind.importStmt:

View File

@ -106,7 +106,6 @@ method dispatchDelayedPragmas(self: BytecodeCompiler, name: Name)
proc funDecl(self: BytecodeCompiler, node: FunDecl, name: Name)
proc compileModule(self: BytecodeCompiler, module: Name)
proc generateCall(self: BytecodeCompiler, fn: Name, args: seq[Expression], line: int)
method prepareFunction(self: BytecodeCompiler, fn: Name)
# End of forward declarations
@ -466,7 +465,7 @@ proc handleBuiltinFunction(self: BytecodeCompiler, fn: Type, args: seq[Expressio
"Identity": Identity
}.to_table()
if fn.builtinOp == "print":
var typ = self.inferOrError(args[0])
var typ = self.inferOrError(args[0]).unwrap()
case typ.kind:
of Int64:
self.emitByte(PrintInt64, line)
@ -799,9 +798,7 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) =
## "Prepares" a function declaration by declaring
## its arguments and typechecking it
# First we declare the function's generics, if it has any.
# This is because the function's return type may in itself
# be a generic, so it needs to exist first
# First we declare the function's generics, if it has any
var constraints: seq[tuple[match: bool, kind: Type]] = @[]
for gen in fn.node.generics:
self.unpackTypes(gen.cond, constraints)
@ -829,8 +826,9 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) =
self.error("cannot declare more than 16777215 variables at a time")
inc(self.stackIndex)
typ = self.inferOrError(argument.valueType)
if typ.kind == Typevar:
typ = typ.wrapped
if self.compare(typ, "auto".toIntrinsic()):
fn.valueType.isAuto = true
typ = "any".toIntrinsic()
self.names.add(Name(depth: fn.depth + 1,
isPrivate: true,
owner: fn.owner,
@ -857,8 +855,8 @@ method prepareFunction(self: BytecodeCompiler, fn: Name) =
# The function needs a return type too!
if not node.returnType.isNil():
fn.valueType.returnType = self.inferOrError(node.returnType)
if fn.valueType.returnType.kind == Typevar:
fn.valueType.returnType = fn.valueType.returnType.wrapped
if self.compare(fn.valueType.returnType, "auto".toIntrinsic()):
fn.valueType.isAuto = true
fn.position = self.stackIndex
self.stackIndex = idx
if node.isTemplate:
@ -1045,17 +1043,17 @@ method literal(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Typ
## as singletons, strings and numbers
case node.kind:
of trueExpr:
result = Type(kind: Bool)
result = "bool".toIntrinsic()
if compile:
self.emitByte(LoadTrue, node.token.line)
of falseExpr:
result = Type(kind: Bool)
result = "bool".toIntrinsic()
if compile:
self.emitByte(LoadFalse, node.token.line)
of strExpr:
result = Type(kind: String)
result = "string".toIntrinsic()
if compile:
self.emitConstant(LiteralExpr(node), Type(kind: String))
self.emitConstant(LiteralExpr(node), result)
of intExpr:
let y = IntExpr(node)
let kind = self.infer(y)
@ -1159,7 +1157,7 @@ method unary(self: BytecodeCompiler, node: UnaryExpr, compile: bool = true): Typ
method binary(self: BytecodeCompiler, node: BinaryExpr, compile: bool = true): Type {.discardable.} =
## Compiles all binary expressions
var default: Expression
let fn = Type(kind: Function, returnType: Type(kind: Any), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)])
let fn = Type(kind: Function, returnType: "any".toIntrinsic(), args: @[("", self.inferOrError(node.a), default), ("", self.inferOrError(node.b), default)])
var impl = self.match(node.token.lexeme, fn, node)
result = impl.valueType
if impl.isGeneric:
@ -1186,7 +1184,7 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com
if s.kind == NameKind.CustomType:
# This makes it so that the type of
# a type comes out as "typevar"
result = Type(kind: Typevar, wrapped: result)
result = result.wrap()
if not compile:
return result
var node = s.ident
@ -1209,29 +1207,16 @@ method identifier(self: BytecodeCompiler, node: IdentExpr, name: Name = nil, com
# to have something on the stack to pop off (just to act as
# a placeholder)
self.emitByte(LoadNil, node.token.line)
elif s.valueType.isBuiltin:
case s.ident.token.lexeme:
of "nil":
self.emitByte(LoadNil, node.token.line)
of "nan":
self.emitByte(LoadNan, node.token.line)
of "inf":
self.emitByte(LoadInf, node.token.line)
else:
discard # Unreachable
else:
if not s.belongsTo.isNil() and s.belongsTo.valueType.fun.kind == funDecl and FunDecl(s.belongsTo.valueType.fun).isTemplate:
discard
if s.depth > 0:
# Loads a regular variable from the current frame
self.emitByte(LoadVar, s.ident.token.line)
else:
if s.depth > 0:
# Loads a regular variable from the current frame
self.emitByte(LoadVar, s.ident.token.line)
else:
# Loads a global variable from an absolute stack
# position
self.emitByte(LoadGlobal, s.ident.token.line)
# No need to check for -1 here: we already did a nil check above!
self.emitBytes(s.position.toTriple(), s.ident.token.line)
# Loads a global variable from an absolute stack
# position
self.emitByte(LoadGlobal, s.ident.token.line)
# No need to check for -1 here: we already did a nil check above!
self.emitBytes(s.position.toTriple(), s.ident.token.line)
method assignment(self: BytecodeCompiler, node: ASTNode, compile: bool = true): Type {.discardable.} =
@ -1332,7 +1317,7 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
case node.callee.kind:
of NodeKind.identExpr:
# Calls like hi()
var impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: All), args: args), node)
var impl = self.match(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: "all".toIntrinsic(), args: args), node)
result = impl.valueType
if impl.isGeneric:
result = self.specialize(impl.valueType, argExpr)
@ -1421,8 +1406,6 @@ method call(self: BytecodeCompiler, node: CallExpr, compile: bool = true): Type
self.error(&"expression has no type", node)
else:
self.error(&"object of type '{self.stringify(typ)}' is not callable", node)
if not result.isNil() and result.kind == Typevar:
result = result.wrapped
method getItemExpr(self: BytecodeCompiler, node: GetItemExpr, compile: bool = true, matching: Type = nil): Type {.discardable.} =
@ -1626,7 +1609,7 @@ method expression(self: BytecodeCompiler, node: Expression, compile: bool = true
proc ifStmt(self: BytecodeCompiler, node: IfStmt) =
## Compiles if/else statements for conditional
## execution of code
self.check(node.condition, Type(kind: Bool))
self.check(node.condition, "bool".toIntrinsic())
self.expression(node.condition)
let jump = self.emitJump(JumpIfFalsePop, node.token.line)
self.statement(node.thenBranch)
@ -1640,7 +1623,7 @@ proc ifStmt(self: BytecodeCompiler, node: IfStmt) =
proc whileStmt(self: BytecodeCompiler, node: WhileStmt) =
## Compiles C-style while loops and
## desugared C-style for loops
self.check(node.condition, Type(kind: Bool))
self.check(node.condition, "bool".toIntrinsic())
let start = self.chunk.code.high()
self.expression(node.condition)
let jump = self.emitJump(JumpIfFalsePop, node.token.line)
@ -1686,7 +1669,7 @@ proc returnStmt(self: BytecodeCompiler, node: ReturnStmt) =
elif not self.currentFunction.valueType.returnType.isNil() and node.value.isNil():
self.error("bare return statement is only allowed in void functions", node)
if not node.value.isNil():
if self.currentFunction.valueType.returnType.kind == Auto:
if self.compare(self.currentFunction.valueType.returnType, "auto".toIntrinsic()):
self.currentFunction.valueType.returnType = self.inferOrError(node.value)
self.check(node.value, self.currentFunction.valueType.returnType)
self.expression(node.value)
@ -1852,7 +1835,7 @@ proc switchStmt(self: BytecodeCompiler, node: SwitchStmt) =
self.emitByte(DupTop, branch.body.token.line)
self.expression(branch.cond)
# We look for a matching equality implementation
fn = Type(kind: Function, returnType: Type(kind: Bool), args: @[("", typeOfA, default), ("", self.inferOrError(branch.cond), default)])
fn = Type(kind: Function, returnType: "bool".toIntrinsic(), args: @[("", typeOfA, default), ("", self.inferOrError(branch.cond), default)])
impl = self.match("==", fn, node)
self.generateCall(impl, @[node.switch, branch.cond], impl.line)
ifJump = self.emitJump(JumpIfFalsePop, branch.body.token.line)
@ -1934,9 +1917,13 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) =
if node.value.isNil():
# Variable has no value: the type declaration
# takes over
if typ.kind == Auto:
# TODO: Implement T.default()!
if self.compare(typ, "auto".toIntrinsic()):
self.error("automatic types require initialization", node)
typ = self.inferOrError(node.valueType)
# One of the few exceptions where we actually don't want to use
# self.compare() is this one, because that will implicitly unwrap
# the typevar and compare the wrapped type, which is not what we want
if typ.kind != Typevar:
self.error(&"expecting type name, got value of type {self.stringify(typ)} instead", node.name)
elif node.valueType.isNil():
@ -1948,7 +1935,7 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) =
# a value: the value's type must match the
# type declaration
let expected = self.inferOrError(node.valueType)
if expected.kind != Auto:
if not self.compare(expected, "auto".toIntrinsic()):
self.check(node.value, expected)
# If this doesn't fail, then we're good
typ = expected
@ -1956,7 +1943,7 @@ proc varDecl(self: BytecodeCompiler, node: VarDecl) =
# Let the compiler infer the type (this
# is the default behavior already, but
# some users may prefer to be explicit!)
typ = self.inferOrError(node.value)
typ = self.inferOrError(node.value)
self.expression(node.value)
self.emitByte(AddVar, node.token.line)
inc(self.stackIndex)

View File

@ -6,6 +6,7 @@ fn sum(a, b: auto): auto {
return a + b;
}
var x: auto = 1;
print(x == 1);
print(sum(1, 2) == 3);

View File

@ -1,6 +1,13 @@
import std;
# Note: unlike C-style casts, casting in peon tells the compiler
# "hey, please reinterpret the underlying bits of data of this thing
# as the type I'm telling you, trust me bro". There is no data conversion
# occurring whatsoever! For that, use converters (once they're implemented LoL)
print(cast[int](2.0) == 4611686018427387904);
print(cast[float](4611686018427387904) == 2.0);
# If that strikes your fancy, you can do this:
var x = int;
var caster = cast[x];
print(caster(2.0) == 4611686018427387904);

View File

@ -12,3 +12,4 @@ fn identity(x: int32): int32 {
fn nope[T: int32 | int16](x: T): T {
return identity(x);
}