mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-11 12:29:46 +00:00
Fix sign extension in arithmetic promotions again.
This commit is contained in:
parent
35bf4ba790
commit
2ff86889db
@ -4,6 +4,7 @@ import millfork.env._
|
||||
import millfork.node._
|
||||
import millfork.error.{ConsoleLogger, Logger}
|
||||
import millfork.assembly.AbstractCode
|
||||
import millfork.output.NoAlignment
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
@ -572,7 +573,12 @@ object AbstractExpressionCompiler {
|
||||
|
||||
def lookupFunction(env: Environment, log: Logger, f: FunctionCallExpression): MangledFunction = {
|
||||
val paramsWithTypes = f.expressions.map(x => getExpressionType(env, log, x) -> x)
|
||||
env.lookupFunction(f.functionName, paramsWithTypes).getOrElse(
|
||||
log.fatal(s"Cannot find function `${f.functionName}` with given params `${paramsWithTypes.map(_._1).mkString("(", ",", ")")}`", f.position))
|
||||
env.lookupFunction(f.functionName, paramsWithTypes).getOrElse {
|
||||
log.error(s"Cannot find function `${f.functionName}` with given params `${paramsWithTypes.map(_._1).mkString("(", ",", ")")}`", f.position)
|
||||
val signature = NormalParamSignature(paramsWithTypes.map { case (t, _) =>
|
||||
UninitializedMemoryVariable("?", t, VariableAllocationMethod.Auto, None, NoAlignment, isVolatile = false)
|
||||
})
|
||||
ExternFunction(f.functionName, NullType, signature, Constant.Zero, env, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import millfork.assembly.m6809.{DAccumulatorIndexed, Indexed, MLine, MOpcode, Tw
|
||||
import millfork.compiler.{AbstractExpressionCompiler, BranchIfFalse, BranchIfTrue, BranchSpec, ComparisonType, CompilationContext, NoBranching}
|
||||
import millfork.node.{DerefExpression, Expression, FunctionCallExpression, GeneratedConstantExpression, IndexedExpression, LhsExpression, LiteralExpression, M6809Register, SumExpression, VariableExpression}
|
||||
import millfork.assembly.m6809.MOpcode._
|
||||
import millfork.env.{AssemblyParamSignature, Constant, ConstantBooleanType, ConstantPointy, ExternFunction, FatBooleanType, M6809RegisterVariable, MathOperator, MemoryVariable, NormalFunction, NormalParamSignature, NumericConstant, StackVariablePointy, Type, Variable, VariablePointy}
|
||||
import millfork.env.{AssemblyParamSignature, Constant, ConstantBooleanType, ConstantPointy, ExternFunction, FatBooleanType, FunctionInMemory, M6809RegisterVariable, MacroFunction, MathOperator, MemoryVariable, NormalFunction, NormalParamSignature, NumericConstant, StackVariablePointy, Type, Variable, VariablePointy}
|
||||
|
||||
import scala.collection.GenTraversableOnce
|
||||
|
||||
@ -285,8 +285,9 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
|
||||
env.maybeGet[Type](fce.functionName) match {
|
||||
case Some(typ) =>
|
||||
val sourceType = validateTypeCastAndGetSourceExpressionType(ctx, typ, params)
|
||||
return sourceType.size match {
|
||||
case 1 => compileToB(ctx, params.head) ++ targetifyB(ctx, target, isSigned = sourceType.isSigned)
|
||||
return typ.size match {
|
||||
// TODO: alternating signedness?
|
||||
case 1 => compileToB(ctx, params.head) ++ targetifyB(ctx, target, isSigned = typ.isSigned)
|
||||
case 2 => compileToD(ctx, params.head) ++ targetifyD(ctx, target)
|
||||
case _ => ???
|
||||
}
|
||||
@ -294,47 +295,71 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
|
||||
// fallthrough to the lookup below
|
||||
}
|
||||
val f = lookupFunction(ctx, fce)
|
||||
val prepareParams: List[MLine] = f.params match {
|
||||
case NormalParamSignature(List(param)) if param.typ.size == 1 =>
|
||||
compileToB(ctx, params.head)
|
||||
case NormalParamSignature(List(param)) if param.typ.size == 2 =>
|
||||
compileToD(ctx, params.head)
|
||||
case NormalParamSignature(signature) =>
|
||||
params.zip(signature).flatMap { case (paramExpr, paramVar) =>
|
||||
val callCtx = callingContext(ctx, f.name, paramVar)
|
||||
paramVar.typ.size match {
|
||||
case 1 =>
|
||||
compileToB(ctx, paramExpr) ++ storeB(callCtx, VariableExpression(paramVar.name + "`aa"))
|
||||
case 2 =>
|
||||
compileToD(ctx, paramExpr) ++ storeD(callCtx, VariableExpression(paramVar.name + "`aa"))
|
||||
case _ =>
|
||||
f match {
|
||||
case function: MacroFunction =>
|
||||
val (paramPreparation, statements) = M6809MacroExpander.inlineFunction(ctx, function, params, expr.position)
|
||||
paramPreparation ++ statements.flatMap { s =>
|
||||
M6809StatementCompiler.compile(ctx, s) match {
|
||||
case (code, Nil) => code
|
||||
case (code, _) => ctx.log.error("Invalid statement in macro expansion", fce.position); code
|
||||
}
|
||||
}
|
||||
case _:FunctionInMemory =>
|
||||
val prepareParams: List[MLine] = f.params match {
|
||||
case NormalParamSignature(List(param)) if param.typ.size == 1 =>
|
||||
compileToB(ctx, params.head)
|
||||
case NormalParamSignature(List(param)) if param.typ.size == 2 =>
|
||||
compileToD(ctx, params.head)
|
||||
case NormalParamSignature(signature) =>
|
||||
params.zip(signature).flatMap { case (paramExpr, paramVar) =>
|
||||
val callCtx = callingContext(ctx, f.name, paramVar)
|
||||
paramVar.typ.size match {
|
||||
case 1 =>
|
||||
compileToB(ctx, paramExpr) ++ storeB(callCtx, VariableExpression(paramVar.name + "`aa"))
|
||||
case 2 =>
|
||||
compileToD(ctx, paramExpr) ++ storeD(callCtx, VariableExpression(paramVar.name + "`aa"))
|
||||
case _ =>
|
||||
???
|
||||
}
|
||||
}
|
||||
case AssemblyParamSignature(signature) =>
|
||||
params.zip(signature).flatMap { case (e, a) =>
|
||||
val compiled = a.variable match {
|
||||
case M6809RegisterVariable(M6809Register.A, _) => compileToA(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.B, _) => compileToB(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.D, _) => compileToD(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.X, _) => compileToX(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.Y, _) => compileToY(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.U, _) => compileToU(ctx, e)
|
||||
case _ => ???
|
||||
}
|
||||
if (compiled.length > 1) ???
|
||||
compiled
|
||||
}
|
||||
case _ => ???
|
||||
}
|
||||
val actualCall = f match {
|
||||
case nf:NormalFunction =>
|
||||
List(MLine.absolute(JSR, nf.toAddress))
|
||||
case nf:ExternFunction =>
|
||||
List(MLine.absolute(JSR, nf.toAddress))
|
||||
case _ =>
|
||||
println("Unsupported function: " + f.name)
|
||||
???
|
||||
}
|
||||
val storeResult = f.returnType.size match {
|
||||
case 1 => targetifyB(ctx, target, f.returnType.isSigned)
|
||||
case 2 => targetifyD(ctx, target)
|
||||
case _ =>
|
||||
if (target == MExpressionTarget.NOTHING) {
|
||||
Nil
|
||||
} else {
|
||||
println("Unsupported function: " + f.name)
|
||||
???
|
||||
}
|
||||
}
|
||||
}
|
||||
case AssemblyParamSignature(signature) =>
|
||||
params.zip(signature).flatMap { case (e, a) =>
|
||||
val compiled = a.variable match {
|
||||
case M6809RegisterVariable(M6809Register.A, _) => compileToA(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.B, _) => compileToB(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.D, _) => compileToD(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.X, _) => compileToX(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.Y, _) => compileToY(ctx, e)
|
||||
case M6809RegisterVariable(M6809Register.U, _) => compileToU(ctx, e)
|
||||
case _ => ???
|
||||
}
|
||||
if (compiled.length > 1) ???
|
||||
compiled
|
||||
}
|
||||
case _ => ???
|
||||
prepareParams ++ actualCall ++ storeResult
|
||||
}
|
||||
val actualCall = f match {
|
||||
case nf:NormalFunction =>
|
||||
List(MLine.absolute(JSR, nf.toAddress))
|
||||
case nf:ExternFunction =>
|
||||
List(MLine.absolute(JSR, nf.toAddress))
|
||||
case _ => ???
|
||||
}
|
||||
prepareParams ++ actualCall
|
||||
}
|
||||
case _ => ???
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
package millfork.compiler.m6809
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
import millfork.assembly.m6809.MLine
|
||||
import millfork.assembly.z80.ZLine
|
||||
import millfork.compiler.{CompilationContext, MacroExpander}
|
||||
import millfork.env._
|
||||
import millfork.node._
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
object M6809MacroExpander extends MacroExpander[MLine] {
|
||||
|
||||
override def prepareAssemblyParams(ctx: CompilationContext, assParams: List[AssemblyParam], params: List[Expression], code: List[ExecutableStatement]): (List[MLine], List[ExecutableStatement]) = {
|
||||
var paramPreparation = List[MLine]()
|
||||
var actualCode = code
|
||||
var hadRegisterParam = false
|
||||
assParams.zip(params).foreach {
|
||||
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
|
||||
actualParam match {
|
||||
case VariableExpression(vname) =>
|
||||
ctx.env.get[ThingInMemory](vname)
|
||||
case l: LhsExpression =>
|
||||
// TODO: ??
|
||||
M6809ExpressionCompiler.compileToB(ctx, l)
|
||||
case _ =>
|
||||
ctx.log.error("A non-assignable expression was passed to an inlineable function as a `ref` parameter", actualParam.position)
|
||||
}
|
||||
actualCode = actualCode.map {
|
||||
case a@MosAssemblyStatement(_, _, expr, _) =>
|
||||
a.copy(expression = expr.replaceVariable(ph, actualParam))
|
||||
case x => x
|
||||
}
|
||||
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByConstant), actualParam) =>
|
||||
ctx.env.eval(actualParam).getOrElse(ctx.env.errorConstant("Non-constant expression was passed to an inlineable function as a `const` parameter", actualParam.position))
|
||||
actualCode = actualCode.map {
|
||||
case a@Z80AssemblyStatement(_, _, _, expr, _) =>
|
||||
a.copy(expression = expr.replaceVariable(ph, actualParam))
|
||||
case x => x
|
||||
}
|
||||
case (AssemblyParam(typ, v@M6809RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
|
||||
if (hadRegisterParam) {
|
||||
ctx.log.error("Only one macro assembly function parameter can be passed via a register", actualParam.position)
|
||||
}
|
||||
hadRegisterParam = true
|
||||
paramPreparation = (register, typ.size) match {
|
||||
case (M6809Register.A, 1) => M6809ExpressionCompiler.compileToA(ctx, actualParam)
|
||||
case (M6809Register.B, 1) => M6809ExpressionCompiler.compileToB(ctx, actualParam)
|
||||
case (M6809Register.D, 2) => M6809ExpressionCompiler.compileToD(ctx, actualParam)
|
||||
case (M6809Register.X, 2) => M6809ExpressionCompiler.compileToX(ctx, actualParam)
|
||||
case (M6809Register.Y, 2) => M6809ExpressionCompiler.compileToY(ctx, actualParam)
|
||||
case (M6809Register.U, 2) => M6809ExpressionCompiler.compileToU(ctx, actualParam)
|
||||
case _ =>
|
||||
ctx.log.error(s"Invalid parameter for macro: ${typ.name} ${register.toString.toLowerCase(Locale.ROOT)}", actualParam.position)
|
||||
Nil
|
||||
}
|
||||
case (AssemblyParam(_, _, AssemblyParameterPassingBehaviour.Copy), actualParam) =>
|
||||
???
|
||||
case (_, actualParam) =>
|
||||
}
|
||||
paramPreparation -> actualCode
|
||||
}
|
||||
}
|
@ -1638,10 +1638,26 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
env.maybeGet[Type](f.functionName) match {
|
||||
case Some(typ) =>
|
||||
val sourceType = validateTypeCastAndGetSourceExpressionType(ctx, typ, params)
|
||||
val newExprTypeAndVariable = exprTypeAndVariable.map { i =>
|
||||
(if (i._1.size == sourceType.size) i._1 else sourceType) -> i._2
|
||||
exprTypeAndVariable match {
|
||||
case None =>
|
||||
return compile(ctx, params.head, None, branches)
|
||||
case Some((_, targetVariable)) =>
|
||||
val targetType = targetVariable.typ
|
||||
val officialType: Type = if (targetType.size == typ.size) {
|
||||
typ
|
||||
} else if (targetType.size == sourceType.size) {
|
||||
targetType
|
||||
} else if (typ.isSigned == sourceType.isSigned) {
|
||||
targetType
|
||||
} else if (typ.size == 1) {
|
||||
return compileToA(ctx, f) ++ expressionStorageFromA(ctx, exprTypeAndVariable, position = expr.position, signedSource = typ.isSigned)
|
||||
} else if (typ.size == 2) {
|
||||
return compileToAX(ctx, f) ++ expressionStorageFromAX(ctx, exprTypeAndVariable, position = expr.position)
|
||||
} else {
|
||||
ctx.log.fatal(s"Cannot compile $typ($sourceType(...)) to $targetType")
|
||||
}
|
||||
return compile(ctx, params.head, Some(officialType -> targetVariable), branches)
|
||||
}
|
||||
return compile(ctx, params.head, newExprTypeAndVariable, branches)
|
||||
case None =>
|
||||
// fallthrough to the lookup below
|
||||
}
|
||||
|
@ -1202,8 +1202,9 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
env.maybeGet[Type](f.functionName) match {
|
||||
case Some(typ) =>
|
||||
val sourceType = validateTypeCastAndGetSourceExpressionType(ctx, typ, params)
|
||||
return sourceType.size match {
|
||||
case 1 => targetifyA(ctx, target, compileToA(ctx, params.head), isSigned = sourceType.isSigned)
|
||||
return typ.size match {
|
||||
// TODO: alternating signedness?
|
||||
case 1 => targetifyA(ctx, target, compileToA(ctx, params.head), isSigned = typ.isSigned)
|
||||
case 2 => targetifyHL(ctx, target, compileToHL(ctx, params.head))
|
||||
case _ => ???
|
||||
}
|
||||
@ -1213,8 +1214,11 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
lookupFunction(ctx, f) match {
|
||||
case function: MacroFunction =>
|
||||
val (paramPreparation, statements) = Z80MacroExpander.inlineFunction(ctx, function, params, expression.position)
|
||||
paramPreparation ++ statements.map {
|
||||
case _ => ???
|
||||
paramPreparation ++ statements.flatMap { s =>
|
||||
Z80StatementCompiler.compile(ctx, s) match {
|
||||
case (code, Nil) => code
|
||||
case (code, _) => ctx.log.error("Invalid statement in macro expansion", expression.position); code
|
||||
}
|
||||
}
|
||||
case function: EmptyFunction =>
|
||||
??? // TODO: type conversion?
|
||||
|
@ -87,6 +87,8 @@ class M6809Assembler(program: Program,
|
||||
index
|
||||
case MLine0(op, NonExistent, _) if MOpcode.NoopDiscard(op) =>
|
||||
index
|
||||
case MLine0(CHANGED_MEM, _, _) =>
|
||||
index
|
||||
case MLine0(TFR, TwoRegisters(source, target), param)
|
||||
if M6809Register.registerSize(source) == M6809Register.registerSize(target) && param.isProvablyZero =>
|
||||
writeByte(bank, index, 0x1f)
|
||||
|
@ -10,7 +10,7 @@ import org.scalatest.{FunSuite, Matchers}
|
||||
class MacroSuite extends FunSuite with Matchers {
|
||||
|
||||
test("Most basic test") {
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086)(
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086, Cpu.Motorola6809)(
|
||||
"""
|
||||
| macro void run(byte x) {
|
||||
| output = x
|
||||
@ -29,7 +29,7 @@ class MacroSuite extends FunSuite with Matchers {
|
||||
}
|
||||
|
||||
test("Macros in assembly") {
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086)(
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086, Cpu.Motorola6809)(
|
||||
"""
|
||||
| macro void run(byte x) {
|
||||
| output = x
|
||||
|
@ -1,7 +1,7 @@
|
||||
package millfork.test
|
||||
|
||||
import millfork.Cpu
|
||||
import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun}
|
||||
import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun, ShouldNotCompile}
|
||||
import org.scalatest.{FunSuite, Matchers}
|
||||
|
||||
/**
|
||||
@ -10,7 +10,7 @@ import org.scalatest.{FunSuite, Matchers}
|
||||
class SignExtensionSuite extends FunSuite with Matchers {
|
||||
|
||||
test("Sbyte to Word") {
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)("""
|
||||
| word output @$c000
|
||||
| void main () {
|
||||
| sbyte b
|
||||
@ -22,7 +22,7 @@ class SignExtensionSuite extends FunSuite with Matchers {
|
||||
}
|
||||
}
|
||||
test("Sbyte to Word 2") {
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)("""
|
||||
| word output @$c000
|
||||
| void main () {
|
||||
| output = b()
|
||||
@ -71,7 +71,7 @@ class SignExtensionSuite extends FunSuite with Matchers {
|
||||
}
|
||||
|
||||
test("Byte to Word") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)("""
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)("""
|
||||
| word output @$c000
|
||||
| void main () {
|
||||
| sbyte b
|
||||
@ -85,7 +85,7 @@ class SignExtensionSuite extends FunSuite with Matchers {
|
||||
}
|
||||
|
||||
test("Byte to Word 2") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)("""
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)("""
|
||||
| word output @$c000
|
||||
| void main () {
|
||||
| sbyte b
|
||||
@ -97,4 +97,31 @@ class SignExtensionSuite extends FunSuite with Matchers {
|
||||
m.readWord(0xc000) should equal(0x00ff)
|
||||
}
|
||||
}
|
||||
|
||||
test("Returning sbyte as word") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)("""
|
||||
| word output @$c000
|
||||
| void main () {
|
||||
| output = f($ff)
|
||||
| }
|
||||
| noinline word f(byte x) = sbyte(x)
|
||||
""".stripMargin){m =>
|
||||
m.readWord(0xc000) should equal(0xffff)
|
||||
}
|
||||
}
|
||||
|
||||
test("Check multilayered conversions of signed and unsigned types") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)(
|
||||
"""
|
||||
|long output @$c000
|
||||
|noinline sbyte f() = $FE
|
||||
|void main() {
|
||||
| // should yield $0000fffe
|
||||
| output = word(f())
|
||||
|}
|
||||
|""".stripMargin) { m =>
|
||||
m.readLong(0xc000) should equal(0xfffe)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user