1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-12-22 16:31:02 +00:00

Runtime bounds checking

This commit is contained in:
Karol Stasiak 2018-01-08 01:18:04 +01:00
parent 502faa3694
commit 33716f3881
4 changed files with 76 additions and 34 deletions

View File

@ -10,6 +10,8 @@ where `11111` is a sequential number and `xx` is the type:
* `an` logical conjunction short-circuiting
* `bc` array bounds checking (`-fbounds-checking`)
* `c8` constant `#8` for `BIT` when immediate addressing is not available
* `co` greater-than comparison

View File

@ -37,9 +37,11 @@
* `-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

View File

@ -178,7 +178,7 @@ object AssemblyLine {
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)

View File

@ -98,9 +98,9 @@ object MlCompiler {
getExpressionType(ctx, param)
b
case IndexedExpression(_, _) => b
case SeparateBytesExpression(h, l) =>
if (getExpressionType(ctx, h).size > 1) ErrorReporting.error("Hi byte too large", h.position)
if (getExpressionType(ctx, l).size > 1) ErrorReporting.error("Lo byte too large", l.position)
case SeparateBytesExpression(hi, lo) =>
if (getExpressionType(ctx, hi).size > 1) ErrorReporting.error("Hi byte too large", hi.position)
if (getExpressionType(ctx, lo).size > 1) ErrorReporting.error("Lo byte too large", lo.position)
w
case SumExpression(params, _) => b
case FunctionCallExpression("not", params) => bool
@ -158,7 +158,7 @@ object MlCompiler {
AssemblyLine(LDA, Immediate, expr.hiByte),
AssemblyLine(LDY, Immediate, expr.loByte))
case m: VariableInMemory =>
val addrMode = if(m.zeropage) ZeroPage else Absolute
val addrMode = if (m.zeropage) ZeroPage else Absolute
val addr = m.toAddress
m.typ.size match {
case 0 => Nil
@ -309,16 +309,16 @@ object MlCompiler {
if (register == Register.A) {
indexRegister match {
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 =>
calculatingIndex ++ List(AssemblyLine.absoluteX(STA, arrayAddr + constIndex))
calculatingIndex ++ arrayBoundsCheck(ctx, arrayName, Register.X, indexExpr) ++ List(AssemblyLine.absoluteX(STA, arrayAddr + constIndex))
}
} else {
indexRegister match {
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 =>
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) {
val label = nextLabel("sx")
AssemblyLine.variable(ctx, STA, target) ++
List(
AssemblyLine.immediate(ORA, 0x7f),
AssemblyLine.relative(BMI, label),
AssemblyLine.immediate(LDA, 0),
AssemblyLine.label(label)) ++
List(
AssemblyLine.immediate(ORA, 0x7f),
AssemblyLine.relative(BMI, label),
AssemblyLine.immediate(LDA, 0),
AssemblyLine.label(label)) ++
List.tabulate(target.typ.size - 1)(i => AssemblyLine.variable(ctx, STA, target, i + 1)).flatten
} else {
AssemblyLine.variable(ctx, STA, target) ++
@ -735,9 +735,9 @@ object MlCompiler {
val calculatingIndex = compile(ctx, variableIndex, Some(b, RegisterVariable(indexRegister, b)), NoBranching)
indexRegister match {
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 =>
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) =>
assertCompatible(exprType, target.typ)
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.AX, _) =>
compile(ctx, l, Some(b -> RegisterVariable(Register.A, b)), branches) ++
@ -929,18 +929,18 @@ object MlCompiler {
// TODO: signed
val (size, signed) = assertComparison(ctx, params)
compileTransitiveRelation(ctx, "<=", params, exprTypeAndVariable, branches) { (l, r) =>
size match {
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)
}
size match {
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 "==" =>
val size = params.map(p => getExpressionType(ctx, p).size).max
compileTransitiveRelation(ctx, "==", params, exprTypeAndVariable, branches) { (l, r) =>
size match {
case 1 => BuiltIns.compileByteComparison(ctx, ComparisonType.Equal, l, r, branches)
case 2 => BuiltIns.compileWordComparison(ctx, ComparisonType.Equal, l, r, branches)
}
size match {
case 1 => BuiltIns.compileByteComparison(ctx, ComparisonType.Equal, l, r, branches)
case 2 => BuiltIns.compileWordComparison(ctx, ComparisonType.Equal, l, r, branches)
}
}
case "!=" =>
val (l, r, size) = assertBinary(ctx, params)
@ -1150,7 +1150,7 @@ object MlCompiler {
operator: String,
params: List[Expression],
exprTypeAndVariable: Option[(Type, Variable)],
branches: BranchSpec)(binary: (Expression, Expression) => List[AssemblyLine]): List[AssemblyLine] ={
branches: BranchSpec)(binary: (Expression, Expression) => List[AssemblyLine]): List[AssemblyLine] = {
params match {
case List(l, r) => binary(l, r)
case List(_) | Nil =>
@ -1158,7 +1158,7 @@ object MlCompiler {
case _ =>
val conjunction = params.init.zip(params.tail).map {
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)
}
}
@ -1355,7 +1355,7 @@ object MlCompiler {
???
case (_, actualParam) =>
}
case NormalParamSignature(Nil) => i.code
case NormalParamSignature(Nil) =>
case NormalParamSignature(normalParams) => ???
}
actualCode
@ -1443,11 +1443,11 @@ object MlCompiler {
statement match {
case AssemblyStatement(o, a, x, e) =>
val c: Constant = x match {
// TODO: hmmm
// TODO: hmmm
case VariableExpression(name) =>
if (OpcodeClasses.ShortBranching(o) || o == JMP || o == LABEL) {
MemoryAddressConstant(Label(name))
} else{
} else {
env.evalForAsm(x).getOrElse(env.get[ThingInMemory](name, x.position).toAddress)
}
case _ =>
@ -1700,14 +1700,14 @@ object MlCompiler {
FunctionCallExpression("<", List(vex, f.end)),
f.body :+ increment),
))
case (ForDirection.To | ForDirection.ParallelTo,_,_) =>
case (ForDirection.To | ForDirection.ParallelTo, _, _) =>
compile(ctx, List(
Assignment(vex, f.start),
WhileStatement(
FunctionCallExpression("<=", List(vex, f.end)),
f.body :+ increment),
))
case (ForDirection.DownTo,_,_) =>
case (ForDirection.DownTo, _, _) =>
compile(ctx, List(
Assignment(vex, f.start),
IfStatement(
@ -1734,4 +1734,42 @@ object MlCompiler {
private def branchChunk(opcode: Opcode.Value, labelName: String) = {
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
}
}
}
}