diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f16dbd7..d1d57f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ * **Potentially breaking change!** Added `min`, `max` and `if` compile-time functions. +* Added experimental `signed16` and `unsigned16` types. + * Added warnings for calling from one segment to another overlapping one. * 6502: Fixed undocumented mnemonics. diff --git a/docs/lang/types.md b/docs/lang/types.md index cb6dbb12..169a474b 100644 --- a/docs/lang/types.md +++ b/docs/lang/types.md @@ -20,11 +20,15 @@ Millfork puts extra limitations on which types can be used in which contexts. * `long` – 4-byte value of undefined signedness, defaulting to unsigned (alias: `int32`) -* `int40`, `int48`,... `int128` – even larger unsigned types +* `int40`, `int48`,... `int128` – even larger types of undefined signedness, defaulting to unsigned -* `sbyte` – signed 1-byte value +* `sbyte` – signed 1-byte value (alias: `signed8`) -* `ubyte` – unsigned 1-byte value +* `ubyte` – unsigned 1-byte value (alias: `unsigned8`) + +* `signed16` – signed 2-byte value (experimental) + +* `unsigned16` – unsigned 2-byte value (experimental) * `pointer` – raw pointers; the same as `word`, but variables of this type default to be zero-page-allocated and you can index `pointer`-typed expressions. diff --git a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala index 2c955f48..8b061e54 100644 --- a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala @@ -1,5 +1,6 @@ package millfork.compiler +import millfork.CompilationFlag import millfork.env._ import millfork.node._ import millfork.error.{ConsoleLogger, Logger} @@ -408,13 +409,28 @@ object AbstractExpressionCompiler { if (getExpressionTypeImpl(env, log, hi, loosely).size > 1) log.error("Hi byte too large", hi.position) if (getExpressionTypeImpl(env, log, lo, loosely).size > 1) log.error("Lo byte too large", lo.position) w - case SumExpression(params, _) => params.map { case (_, e) => getExpressionTypeImpl(env, log, e, loosely).size }.max match { - case 1 => b - case 2 => w - case 3 => env.get[Type]("int24") - case 4 => env.get[Type]("int32") - case _ => log.error("Adding values bigger than longs", expr.position); env.get[Type]("int32") - } + case SumExpression(params, _) => + val paramTypes = params.map { case (_, e) => getExpressionTypeImpl(env, log, e, loosely) } + val anySigned = paramTypes.exists(_.isSigned) + val anyUnsigned = paramTypes.exists { + case p: DerivedPlainType => !p.isSigned + case _ => false + } + paramTypes.map(_.size).max match { + case 1 => + params match { + case List((false, LiteralExpression(0, _)), (true, _)) if !anyUnsigned => env.get[Type]("sbyte") + case _ => if (anySigned && !anyUnsigned) env.get[Type]("sbyte") else b + } + case 2 => + params match { + case List((false, LiteralExpression(0, _)), (true, _)) if !anyUnsigned => env.get[Type]("signed16") + case _ => if (anySigned && !anyUnsigned) env.get[Type]("signed16") else w + } + case 3 => env.get[Type]("int24") + case 4 => env.get[Type]("int32") + case _ => log.error("Adding values bigger than longs", expr.position); env.get[Type]("int32") + } case FunctionCallExpression("nonet", _) => w case FunctionCallExpression("not", params) => toAllBooleanConstants(params) match { diff --git a/src/main/scala/millfork/compiler/mos/BuiltIns.scala b/src/main/scala/millfork/compiler/mos/BuiltIns.scala index 03434af6..caefb989 100644 --- a/src/main/scala/millfork/compiler/mos/BuiltIns.scala +++ b/src/main/scala/millfork/compiler/mos/BuiltIns.scala @@ -982,28 +982,51 @@ object BuiltIns { case ComparisonType.LessSigned => val fixup = ctx.nextLabel("co") cmpTo(LDA, ll) ++ - List(AssemblyLine.implied(SEC)) ++ - cmpTo(SBC, rl) ++ + cmpTo(CMP, rl) ++ cmpTo(LDA, lh) ++ cmpTo(SBC, rh) ++ List( AssemblyLine.relative(BVC, fixup), AssemblyLine.immediate(EOR, 0x80), - AssemblyLine.label(fixup)) - List(AssemblyLine.relative(BCC, x)) + AssemblyLine.label(fixup))++ + List(AssemblyLine.relative(BMI, x)) case ComparisonType.GreaterOrEqualSigned => val fixup = ctx.nextLabel("co") cmpTo(LDA, ll) ++ - List(AssemblyLine.implied(SEC)) ++ - cmpTo(SBC, rl) ++ + cmpTo(CMP, rl) ++ cmpTo(LDA, lh) ++ cmpTo(SBC, rh) ++ List( AssemblyLine.relative(BVC, fixup), AssemblyLine.immediate(EOR, 0x80), - AssemblyLine.label(fixup)) - List(AssemblyLine.relative(BCS, x)) + AssemblyLine.label(fixup))++ + List(AssemblyLine.relative(BPL, x)) + + case ComparisonType.GreaterSigned => + val fixup = ctx.nextLabel("co") + cmpTo(LDA, rl) ++ + cmpTo(CMP, ll) ++ + cmpTo(LDA, rh) ++ + cmpTo(SBC, lh) ++ + List( + AssemblyLine.relative(BVC, fixup), + AssemblyLine.immediate(EOR, 0x80), + AssemblyLine.label(fixup))++ + List(AssemblyLine.relative(BMI, x)) + + case ComparisonType.LessOrEqualSigned => + val fixup = ctx.nextLabel("co") + cmpTo(LDA, rl) ++ + cmpTo(CMP, ll) ++ + cmpTo(LDA, rh) ++ + cmpTo(SBC, lh) ++ + List( + AssemblyLine.relative(BVC, fixup), + AssemblyLine.immediate(EOR, 0x80), + AssemblyLine.label(fixup))++ + List(AssemblyLine.relative(BPL, x)) + case _ => ??? // TODO: signed word comparisons: <=, > } diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 8e73f8f8..8a68c932 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -431,12 +431,13 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa addThing(Alias("unsigned8", "ubyte"), None) addThing(Alias("signed8", "sbyte"), None) addThing(DerivedPlainType("unsigned16", w, isSigned = false, isPointy = false), None) + addThing(DerivedPlainType("signed16", w, isSigned = true, isPointy = false), None) for (bits <- Seq(24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128)) { addThing(DerivedPlainType("unsigned" + bits, get[BasicPlainType]("int" + bits), isSigned = false, isPointy = false), None) } if (options.flag(CompilationFlag.EnableInternalTestSyntax)) { - for (bits <- Seq(16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128)) { - addThing(DerivedPlainType("signed" + bits, get[BasicPlainType]("int" + bits), isSigned = false, isPointy = false), None) + for (bits <- Seq(24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128)) { + addThing(DerivedPlainType("signed" + bits, get[BasicPlainType]("int" + bits), isSigned = true, isPointy = false), None) } } val trueType = ConstantBooleanType("true$", value = true) @@ -2418,9 +2419,10 @@ object Environment { val invalidNewIdentifiers: Set[String] = Set( "byte", "sbyte", "word", "pointer", "void", "long", "bool", "set_carry", "set_zero", "set_overflow", "set_negative", - "clear_carry", "clear_zero", "clear_overflow", "clear_negative", - "int8", "int16", "int24", "int32", "int40", "int48", "int56", "int64", "int72", "int80", "int88", "int96", "int104", "int112", "int120", "int128", - "signed8", "unsigned16") ++ neverValidTypeIdentifiers + "clear_carry", "clear_zero", "clear_overflow", "clear_negative") ++ + Seq.iterate(8, 16)(_ + 8).map("int" + _) ++ + Seq.iterate(8, 16)(_ + 8).map("unsigned" + _) ++ + Seq.iterate(8, 16)(_ + 8).map("signed" + _) ++ neverValidTypeIdentifiers // built-in special-cased field names; can be considered keywords by some: val invalidFieldNames: Set[String] = Set("addr", "rawaddr", "pointer", "return") } diff --git a/src/test/scala/millfork/test/ComparisonSuite.scala b/src/test/scala/millfork/test/ComparisonSuite.scala index 3a1bd2bf..7e6b1b35 100644 --- a/src/test/scala/millfork/test/ComparisonSuite.scala +++ b/src/test/scala/millfork/test/ComparisonSuite.scala @@ -1,13 +1,13 @@ package millfork.test import millfork.Cpu -import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuSuperOptimizedRun, EmuUltraBenchmarkRun, EmuUnoptimizedCrossPlatformRun} -import org.scalatest.{FunSuite, Matchers} +import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuSuperOptimizedRun, EmuUltraBenchmarkRun, EmuUnoptimizedCrossPlatformRun, EmuUnoptimizedRun} +import org.scalatest.{AppendedClues, FunSuite, Matchers} /** * @author Karol Stasiak */ -class ComparisonSuite extends FunSuite with Matchers { +class ComparisonSuite extends FunSuite with Matchers with AppendedClues { test("Equality and inequality") { EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)( @@ -653,4 +653,48 @@ class ComparisonSuite extends FunSuite with Matchers { } } + test("Signed16 comparisons") { + for{ + x <- Seq(-30000, -10000, -1, 0, 1, 10000, 30000) + y <- Seq(-30000, -10000, -1, 0, 1, 10000, 30000) + } { + val code = + s""" + | byte output @$$c000 + | + | const sbyte convert(bool f) { + | if f { return 1 } + | return -1 + | } + | + | noinline void testGE(signed16 x, signed16 y, sbyte f) { + | if x >= y { output += f } else { output -= f } + | } + | + | noinline void testLE(signed16 x, signed16 y, sbyte f) { + | if x <= y { output += f } else { output -= f } + | } + | + | noinline void testL(signed16 x, signed16 y, sbyte f) { + | if x < y { output += f } else { output -= f } + | } + | + | noinline void testG(signed16 x, signed16 y, sbyte f) { + | if x > y { output += f } else { output -= f } + | } + | + | void main() { + | output = 0 + | testGE($x, $y, convert(${x >= y})) + | testLE($x, $y, convert(${x <= y})) + | testG($x, $y, convert(${x > y})) + | testL($x, $y, convert(${x < y})) + | } + |""".stripMargin + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(code) { m => + m.readByte(0xc000) should equal (4) withClue s"[$x <=> $y]" + } + } + } + } diff --git a/src/test/scala/millfork/test/SignExtensionSuite.scala b/src/test/scala/millfork/test/SignExtensionSuite.scala index 4aecfc2b..023ef104 100644 --- a/src/test/scala/millfork/test/SignExtensionSuite.scala +++ b/src/test/scala/millfork/test/SignExtensionSuite.scala @@ -124,4 +124,52 @@ class SignExtensionSuite extends FunSuite with Matchers { } } + test("The silliest thing") { + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)( + """ + |word output @$c000 + |word output2 @$c002 + |void main() { + | output = -1 + | output2 = -30000 + |} + |""".stripMargin) { m => + m.readWord(0xc000) should equal(0xffff) + m.readWord(0xc002).toShort.toInt should equal(-30000) + } + } + + test("Signed16 to int32 extension") { + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)( + """ + |int32 output @$c000 + | + |void main() { + | static volatile signed16 tmp + | tmp = -1 + | memory_barrier() + | output = tmp + |} + |""".stripMargin){ m => + m.readLong(0xc000) should equal(-1) + } + } + + test("Signed16 to int32 extension 2") { + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)( + """ + |int32 output @$c000 + | + |void main() { + | static volatile signed16 tmp + | tmp = -1 + | output = 0 + | memory_barrier() + | output += tmp + |} + |""".stripMargin){ m => + m.readLong(0xc000) should equal(-1) + } + } + }