mirror of
https://github.com/KarolS/millfork.git
synced 2024-09-28 18:55:09 +00:00
Runtime bounds checking
This commit is contained in:
parent
502faa3694
commit
33716f3881
@ -10,6 +10,8 @@ where `11111` is a sequential number and `xx` is the type:
|
|||||||
|
|
||||||
* `an` – logical conjunction short-circuiting
|
* `an` – logical conjunction short-circuiting
|
||||||
|
|
||||||
|
* `bc` – array bounds checking (`-fbounds-checking`)
|
||||||
|
|
||||||
* `c8` – constant `#8` for `BIT` when immediate addressing is not available
|
* `c8` – constant `#8` for `BIT` when immediate addressing is not available
|
||||||
|
|
||||||
* `co` – greater-than comparison
|
* `co` – greater-than comparison
|
||||||
|
@ -37,9 +37,11 @@
|
|||||||
|
|
||||||
* `-fjmp-fix`, `-fno-jmp-fix` – Whether should prevent indirect JMP bug on page boundary. `.ini` equivalent: `prevent_jmp_indirect_bug`.
|
* `-fjmp-fix`, `-fno-jmp-fix` – Whether should prevent indirect JMP bug on page boundary. `.ini` equivalent: `prevent_jmp_indirect_bug`.
|
||||||
|
|
||||||
* `-fdecimal-mode`, `-fno-decimal-mode` – Whether should decimal mode be available.` .ini` equivalent: `decimal_mode`.
|
* `-fdecimal-mode`, `-fno-decimal-mode` – Whether decimal mode should be available. `.ini` equivalent: `decimal_mode`.
|
||||||
|
|
||||||
* `-fvariable-overlap`, `-fno-variable-overlap` – Whether should variables overlap if their scopes do not intersect. Default: yes.
|
* `-fvariable-overlap`, `-fno-variable-overlap` – Whether variables should overlap if their scopes do not intersect. Default: yes.
|
||||||
|
|
||||||
|
* `-fbounds-checking`, `-fnobounds-checking` – Whether should insert bounds checking on array access. Default: no.
|
||||||
|
|
||||||
## Optimization options
|
## Optimization options
|
||||||
|
|
||||||
|
@ -178,7 +178,7 @@ object AssemblyLine {
|
|||||||
|
|
||||||
def discardYF() = AssemblyLine(DISCARD_YF, AddrMode.DoesNotExist, Constant.Zero)
|
def discardYF() = AssemblyLine(DISCARD_YF, AddrMode.DoesNotExist, Constant.Zero)
|
||||||
|
|
||||||
def immediate(opcode: Opcode.Value, value: Int) = AssemblyLine(opcode, AddrMode.Immediate, NumericConstant(value, 1))
|
def immediate(opcode: Opcode.Value, value: Long) = AssemblyLine(opcode, AddrMode.Immediate, NumericConstant(value, 1))
|
||||||
|
|
||||||
def immediate(opcode: Opcode.Value, value: Constant) = AssemblyLine(opcode, AddrMode.Immediate, value)
|
def immediate(opcode: Opcode.Value, value: Constant) = AssemblyLine(opcode, AddrMode.Immediate, value)
|
||||||
|
|
||||||
|
@ -98,9 +98,9 @@ object MlCompiler {
|
|||||||
getExpressionType(ctx, param)
|
getExpressionType(ctx, param)
|
||||||
b
|
b
|
||||||
case IndexedExpression(_, _) => b
|
case IndexedExpression(_, _) => b
|
||||||
case SeparateBytesExpression(h, l) =>
|
case SeparateBytesExpression(hi, lo) =>
|
||||||
if (getExpressionType(ctx, h).size > 1) ErrorReporting.error("Hi byte too large", h.position)
|
if (getExpressionType(ctx, hi).size > 1) ErrorReporting.error("Hi byte too large", hi.position)
|
||||||
if (getExpressionType(ctx, l).size > 1) ErrorReporting.error("Lo byte too large", l.position)
|
if (getExpressionType(ctx, lo).size > 1) ErrorReporting.error("Lo byte too large", lo.position)
|
||||||
w
|
w
|
||||||
case SumExpression(params, _) => b
|
case SumExpression(params, _) => b
|
||||||
case FunctionCallExpression("not", params) => bool
|
case FunctionCallExpression("not", params) => bool
|
||||||
@ -158,7 +158,7 @@ object MlCompiler {
|
|||||||
AssemblyLine(LDA, Immediate, expr.hiByte),
|
AssemblyLine(LDA, Immediate, expr.hiByte),
|
||||||
AssemblyLine(LDY, Immediate, expr.loByte))
|
AssemblyLine(LDY, Immediate, expr.loByte))
|
||||||
case m: VariableInMemory =>
|
case m: VariableInMemory =>
|
||||||
val addrMode = if(m.zeropage) ZeroPage else Absolute
|
val addrMode = if (m.zeropage) ZeroPage else Absolute
|
||||||
val addr = m.toAddress
|
val addr = m.toAddress
|
||||||
m.typ.size match {
|
m.typ.size match {
|
||||||
case 0 => Nil
|
case 0 => Nil
|
||||||
@ -309,16 +309,16 @@ object MlCompiler {
|
|||||||
if (register == Register.A) {
|
if (register == Register.A) {
|
||||||
indexRegister match {
|
indexRegister match {
|
||||||
case Register.Y =>
|
case Register.Y =>
|
||||||
calculatingIndex ++ List(AssemblyLine.absoluteY(STA, arrayAddr + constIndex))
|
calculatingIndex ++ arrayBoundsCheck(ctx, arrayName, Register.Y, indexExpr) ++ List(AssemblyLine.absoluteY(STA, arrayAddr + constIndex))
|
||||||
case Register.X =>
|
case Register.X =>
|
||||||
calculatingIndex ++ List(AssemblyLine.absoluteX(STA, arrayAddr + constIndex))
|
calculatingIndex ++ arrayBoundsCheck(ctx, arrayName, Register.X, indexExpr) ++ List(AssemblyLine.absoluteX(STA, arrayAddr + constIndex))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
indexRegister match {
|
indexRegister match {
|
||||||
case Register.Y =>
|
case Register.Y =>
|
||||||
calculatingIndex ++ List(AssemblyLine.implied(transferToA), AssemblyLine.absoluteY(STA, arrayAddr + constIndex))
|
calculatingIndex ++ arrayBoundsCheck(ctx, arrayName, Register.Y, indexExpr) ++ List(AssemblyLine.implied(transferToA), AssemblyLine.absoluteY(STA, arrayAddr + constIndex))
|
||||||
case Register.X =>
|
case Register.X =>
|
||||||
calculatingIndex ++ List(AssemblyLine.implied(transferToA), AssemblyLine.absoluteX(STA, arrayAddr + constIndex))
|
calculatingIndex ++ arrayBoundsCheck(ctx, arrayName, Register.X, indexExpr) ++ List(AssemblyLine.implied(transferToA), AssemblyLine.absoluteX(STA, arrayAddr + constIndex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -711,11 +711,11 @@ object MlCompiler {
|
|||||||
else if (target.typ.isSigned) {
|
else if (target.typ.isSigned) {
|
||||||
val label = nextLabel("sx")
|
val label = nextLabel("sx")
|
||||||
AssemblyLine.variable(ctx, STA, target) ++
|
AssemblyLine.variable(ctx, STA, target) ++
|
||||||
List(
|
List(
|
||||||
AssemblyLine.immediate(ORA, 0x7f),
|
AssemblyLine.immediate(ORA, 0x7f),
|
||||||
AssemblyLine.relative(BMI, label),
|
AssemblyLine.relative(BMI, label),
|
||||||
AssemblyLine.immediate(LDA, 0),
|
AssemblyLine.immediate(LDA, 0),
|
||||||
AssemblyLine.label(label)) ++
|
AssemblyLine.label(label)) ++
|
||||||
List.tabulate(target.typ.size - 1)(i => AssemblyLine.variable(ctx, STA, target, i + 1)).flatten
|
List.tabulate(target.typ.size - 1)(i => AssemblyLine.variable(ctx, STA, target, i + 1)).flatten
|
||||||
} else {
|
} else {
|
||||||
AssemblyLine.variable(ctx, STA, target) ++
|
AssemblyLine.variable(ctx, STA, target) ++
|
||||||
@ -735,9 +735,9 @@ object MlCompiler {
|
|||||||
val calculatingIndex = compile(ctx, variableIndex, Some(b, RegisterVariable(indexRegister, b)), NoBranching)
|
val calculatingIndex = compile(ctx, variableIndex, Some(b, RegisterVariable(indexRegister, b)), NoBranching)
|
||||||
indexRegister match {
|
indexRegister match {
|
||||||
case Register.Y =>
|
case Register.Y =>
|
||||||
calculatingIndex ++ List(AssemblyLine.absoluteY(load, arrayAddr + constantIndex))
|
calculatingIndex ++ arrayBoundsCheck(ctx, arrayName, Register.Y, indexExpr) ++ List(AssemblyLine.absoluteY(load, arrayAddr + constantIndex))
|
||||||
case Register.X =>
|
case Register.X =>
|
||||||
calculatingIndex ++ List(AssemblyLine.absoluteX(load, arrayAddr + constantIndex))
|
calculatingIndex ++ arrayBoundsCheck(ctx, arrayName, Register.X, indexExpr) ++ List(AssemblyLine.absoluteX(load, arrayAddr + constantIndex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -797,7 +797,7 @@ object MlCompiler {
|
|||||||
} { case (exprType, target) =>
|
} { case (exprType, target) =>
|
||||||
assertCompatible(exprType, target.typ)
|
assertCompatible(exprType, target.typ)
|
||||||
target match {
|
target match {
|
||||||
// TODO: some more complex ones may not work correctly
|
// TODO: some more complex ones may not work correctly
|
||||||
case RegisterVariable(Register.A | Register.X | Register.Y, _) => compile(ctx, l, exprTypeAndVariable, branches)
|
case RegisterVariable(Register.A | Register.X | Register.Y, _) => compile(ctx, l, exprTypeAndVariable, branches)
|
||||||
case RegisterVariable(Register.AX, _) =>
|
case RegisterVariable(Register.AX, _) =>
|
||||||
compile(ctx, l, Some(b -> RegisterVariable(Register.A, b)), branches) ++
|
compile(ctx, l, Some(b -> RegisterVariable(Register.A, b)), branches) ++
|
||||||
@ -929,18 +929,18 @@ object MlCompiler {
|
|||||||
// TODO: signed
|
// TODO: signed
|
||||||
val (size, signed) = assertComparison(ctx, params)
|
val (size, signed) = assertComparison(ctx, params)
|
||||||
compileTransitiveRelation(ctx, "<=", params, exprTypeAndVariable, branches) { (l, r) =>
|
compileTransitiveRelation(ctx, "<=", params, exprTypeAndVariable, branches) { (l, r) =>
|
||||||
size match {
|
size match {
|
||||||
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.LessOrEqualSigned else ComparisonType.LessOrEqualUnsigned, l, r, branches)
|
case 1 => BuiltIns.compileByteComparison(ctx, if (signed) ComparisonType.LessOrEqualSigned else ComparisonType.LessOrEqualUnsigned, l, r, branches)
|
||||||
case 2 => BuiltIns.compileWordComparison(ctx, if (signed) ComparisonType.LessOrEqualSigned else ComparisonType.LessOrEqualUnsigned, l, r, branches)
|
case 2 => BuiltIns.compileWordComparison(ctx, if (signed) ComparisonType.LessOrEqualSigned else ComparisonType.LessOrEqualUnsigned, l, r, branches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "==" =>
|
case "==" =>
|
||||||
val size = params.map(p => getExpressionType(ctx, p).size).max
|
val size = params.map(p => getExpressionType(ctx, p).size).max
|
||||||
compileTransitiveRelation(ctx, "==", params, exprTypeAndVariable, branches) { (l, r) =>
|
compileTransitiveRelation(ctx, "==", params, exprTypeAndVariable, branches) { (l, r) =>
|
||||||
size match {
|
size match {
|
||||||
case 1 => BuiltIns.compileByteComparison(ctx, ComparisonType.Equal, l, r, branches)
|
case 1 => BuiltIns.compileByteComparison(ctx, ComparisonType.Equal, l, r, branches)
|
||||||
case 2 => BuiltIns.compileWordComparison(ctx, ComparisonType.Equal, l, r, branches)
|
case 2 => BuiltIns.compileWordComparison(ctx, ComparisonType.Equal, l, r, branches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "!=" =>
|
case "!=" =>
|
||||||
val (l, r, size) = assertBinary(ctx, params)
|
val (l, r, size) = assertBinary(ctx, params)
|
||||||
@ -1150,7 +1150,7 @@ object MlCompiler {
|
|||||||
operator: String,
|
operator: String,
|
||||||
params: List[Expression],
|
params: List[Expression],
|
||||||
exprTypeAndVariable: Option[(Type, Variable)],
|
exprTypeAndVariable: Option[(Type, Variable)],
|
||||||
branches: BranchSpec)(binary: (Expression, Expression) => List[AssemblyLine]): List[AssemblyLine] ={
|
branches: BranchSpec)(binary: (Expression, Expression) => List[AssemblyLine]): List[AssemblyLine] = {
|
||||||
params match {
|
params match {
|
||||||
case List(l, r) => binary(l, r)
|
case List(l, r) => binary(l, r)
|
||||||
case List(_) | Nil =>
|
case List(_) | Nil =>
|
||||||
@ -1158,7 +1158,7 @@ object MlCompiler {
|
|||||||
case _ =>
|
case _ =>
|
||||||
val conjunction = params.init.zip(params.tail).map {
|
val conjunction = params.init.zip(params.tail).map {
|
||||||
case (l, r) => FunctionCallExpression(operator, List(l, r))
|
case (l, r) => FunctionCallExpression(operator, List(l, r))
|
||||||
}.reduceLeft((a,b) => FunctionCallExpression("&&", List(a, b)))
|
}.reduceLeft((a, b) => FunctionCallExpression("&&", List(a, b)))
|
||||||
compile(ctx, conjunction, exprTypeAndVariable, branches)
|
compile(ctx, conjunction, exprTypeAndVariable, branches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1355,7 +1355,7 @@ object MlCompiler {
|
|||||||
???
|
???
|
||||||
case (_, actualParam) =>
|
case (_, actualParam) =>
|
||||||
}
|
}
|
||||||
case NormalParamSignature(Nil) => i.code
|
case NormalParamSignature(Nil) =>
|
||||||
case NormalParamSignature(normalParams) => ???
|
case NormalParamSignature(normalParams) => ???
|
||||||
}
|
}
|
||||||
actualCode
|
actualCode
|
||||||
@ -1443,11 +1443,11 @@ object MlCompiler {
|
|||||||
statement match {
|
statement match {
|
||||||
case AssemblyStatement(o, a, x, e) =>
|
case AssemblyStatement(o, a, x, e) =>
|
||||||
val c: Constant = x match {
|
val c: Constant = x match {
|
||||||
// TODO: hmmm
|
// TODO: hmmm
|
||||||
case VariableExpression(name) =>
|
case VariableExpression(name) =>
|
||||||
if (OpcodeClasses.ShortBranching(o) || o == JMP || o == LABEL) {
|
if (OpcodeClasses.ShortBranching(o) || o == JMP || o == LABEL) {
|
||||||
MemoryAddressConstant(Label(name))
|
MemoryAddressConstant(Label(name))
|
||||||
} else{
|
} else {
|
||||||
env.evalForAsm(x).getOrElse(env.get[ThingInMemory](name, x.position).toAddress)
|
env.evalForAsm(x).getOrElse(env.get[ThingInMemory](name, x.position).toAddress)
|
||||||
}
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
@ -1700,14 +1700,14 @@ object MlCompiler {
|
|||||||
FunctionCallExpression("<", List(vex, f.end)),
|
FunctionCallExpression("<", List(vex, f.end)),
|
||||||
f.body :+ increment),
|
f.body :+ increment),
|
||||||
))
|
))
|
||||||
case (ForDirection.To | ForDirection.ParallelTo,_,_) =>
|
case (ForDirection.To | ForDirection.ParallelTo, _, _) =>
|
||||||
compile(ctx, List(
|
compile(ctx, List(
|
||||||
Assignment(vex, f.start),
|
Assignment(vex, f.start),
|
||||||
WhileStatement(
|
WhileStatement(
|
||||||
FunctionCallExpression("<=", List(vex, f.end)),
|
FunctionCallExpression("<=", List(vex, f.end)),
|
||||||
f.body :+ increment),
|
f.body :+ increment),
|
||||||
))
|
))
|
||||||
case (ForDirection.DownTo,_,_) =>
|
case (ForDirection.DownTo, _, _) =>
|
||||||
compile(ctx, List(
|
compile(ctx, List(
|
||||||
Assignment(vex, f.start),
|
Assignment(vex, f.start),
|
||||||
IfStatement(
|
IfStatement(
|
||||||
@ -1734,4 +1734,42 @@ object MlCompiler {
|
|||||||
private def branchChunk(opcode: Opcode.Value, labelName: String) = {
|
private def branchChunk(opcode: Opcode.Value, labelName: String) = {
|
||||||
LinearChunk(List(AssemblyLine.relative(opcode, Label(labelName))))
|
LinearChunk(List(AssemblyLine.relative(opcode, Label(labelName))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def arrayBoundsCheck(ctx: CompilationContext, arrayName: String, register: Register.Value, index: Expression): List[AssemblyLine] = {
|
||||||
|
if (!ctx.options.flags(CompilationFlag.CheckIndexOutOfBounds)) return Nil
|
||||||
|
ctx.env.maybeGet[ConstantThing](arrayName + ".length") match {
|
||||||
|
case None => Nil
|
||||||
|
case Some(thing) => thing.value match {
|
||||||
|
case NumericConstant(arrayLength, _) =>
|
||||||
|
ctx.env.eval(index) match {
|
||||||
|
case Some(NumericConstant(i, _)) =>
|
||||||
|
if (i >= 0) {
|
||||||
|
if (i < arrayLength) return Nil
|
||||||
|
if (i >= arrayLength) return List(
|
||||||
|
AssemblyLine.implied(PHP),
|
||||||
|
AssemblyLine.absolute(JSR, ctx.env.get[ThingInMemory]("_panic")))
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
if (arrayLength > 0 && arrayLength < 255) {
|
||||||
|
val label = nextLabel("bc")
|
||||||
|
val compare = register match {
|
||||||
|
case Register.A => CMP
|
||||||
|
case Register.X => CPX
|
||||||
|
case Register.Y => CPY
|
||||||
|
}
|
||||||
|
List(
|
||||||
|
AssemblyLine.implied(PHP),
|
||||||
|
AssemblyLine.immediate(compare, arrayLength),
|
||||||
|
AssemblyLine.relative(BCC, label),
|
||||||
|
AssemblyLine.absolute(JSR, ctx.env.get[ThingInMemory]("_panic")),
|
||||||
|
AssemblyLine.label(label),
|
||||||
|
AssemblyLine.implied(PLP))
|
||||||
|
} else {
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
case _ => Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user