1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-05-31 18:41:30 +00:00
millfork/src/test/scala/millfork/test/ByteMathSuite.scala
2021-09-18 00:36:16 +02:00

486 lines
14 KiB
Scala

package millfork.test
import millfork.Cpu
import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuUltraBenchmarkRun, EmuUnoptimizedCrossPlatformRun}
import org.scalatest.{AppendedClues, FunSuite, Matchers}
/**
* @author Karol Stasiak
*/
class ByteMathSuite extends FunSuite with Matchers with AppendedClues {
test("Complex expression") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
"""
| byte output @$c000
| void main () {
| output = (one() + one()) | (((one()<<2)-1) ^ one())
| }
| byte one() {
| return 1
| }
""".stripMargin)(_.readByte(0xc000) should equal(2))
}
test("Complex expression 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
"""
| byte output @$c000
| void main () {
| output = 50
| f()
| }
| noinline void f() {
| byte a
| a = g()
| output -= a >> 1
| }
| noinline byte g() { return 3 }
|
""".stripMargin)(_.readByte(0xc000) should equal(49))
}
test("Byte addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
"""
| byte output @$c000, a
| void main () {
| a = 1
| output = a + a
| }
""".stripMargin)(_.readByte(0xc000) should equal(2))
}
test("Byte addition 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
"""
| byte output @$c000, a
| void main () {
| a = 1
| output = a + 65
| }
""".stripMargin)(_.readByte(0xc000) should equal(66))
}
test("In-place byte addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
"""
| array output[3] @$c000
| byte a
| void main () {
| a = 1
| output[1] = 5
| output[a] += 1
| output[a] += 36
| }
""".stripMargin)(_.readByte(0xc001) should equal(42))
}
test("LHS evaluation during in-place byte addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
"""
| array output[1] @$c000
| byte call_count @$c001
| void main () {
| output[0] = 1
| output[identity(0)] += identity(1)
| }
| noinline byte identity(byte a) {
| call_count += 1
| return a
| }
""".stripMargin){m =>
m.readByte(0xc000) should equal(2)
// TODO: currently the compiler emits separate evaluations of the left hand side for reading and writing
// m.readByte(0xc001) should equal(2)
}
}
test("Parameter order") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
"""
| byte output @$c000
| array arr[6]
| void main () {
| output = 42
| }
| byte test1(byte a) @$6000 {
| return 5 + a
| }
| byte test2(byte a) @$6100 {
| return 5 | a
| }
| byte test3(byte a) @$6200 {
| return a + arr[a]
| }
""".stripMargin)(_.readByte(0xc000) should equal(42))
}
test("In-place byte addition 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
"""
| array output[3] @$c000
| void main () {
| byte x, y, tmpx, tmpy
| tmpx = one()
| tmpy = one()
| x = tmpx
| y = tmpy
| output[y] = 36
| output[x] += 1
| }
| byte one() { return 1 }
""".stripMargin)(_.readByte(0xc001) should equal(37))
}
test("In-place byte multiplication") {
multiplyCase1(0, 0)
multiplyCase1(0, 1)
multiplyCase1(0, 2)
multiplyCase1(0, 5)
multiplyCase1(1, 0)
multiplyCase1(5, 0)
multiplyCase1(7, 0)
multiplyCase1(2, 5)
multiplyCase1(7, 2)
multiplyCase1(100, 2)
multiplyCase1(54, 4)
multiplyCase1(2, 100)
multiplyCase1(4, 54)
}
private def multiplyCase1(x: Int, y: Int): Unit = {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
s"""
| import zp_reg
| byte output @$$c000
| void main () {
| output = $x
| output *= $y
| }
""".
stripMargin)(_.readByte(0xc000) should equal(x * y))
}
test("Byte multiplication") {
multiplyCase2(0, 0)
multiplyCase2(0, 1)
multiplyCase2(0, 2)
multiplyCase2(0, 5)
multiplyCase2(1, 0)
multiplyCase2(5, 0)
multiplyCase2(7, 0)
multiplyCase2(2, 5)
multiplyCase2(7, 2)
multiplyCase2(100, 2)
multiplyCase2(54, 4)
multiplyCase2(2, 100)
multiplyCase2(4, 54)
}
private def multiplyCase2(x: Int, y: Int): Unit = {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
s"""
| import zp_reg
| byte output @$$c000
| void main () {
| byte a
| a = $x
| output = a * $y
| }
""".
stripMargin)(_.readByte(0xc000) should equal(x * y) withClue s"= $x * $y")
}
test("Byte multiplication 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.R800, Cpu.Motorola6809)(
"""
| import zp_reg
| byte output1 @$c001, output2 @$c002, output3 @$c003
| void main () {
| calc1()
| crash_if_bad()
| calc2()
| crash_if_bad()
| calc3()
| crash_if_bad()
| }
|
| byte three() { return 3 }
| byte four() { return 4 }
| noinline byte five() { return 5 }
|
| noinline void calc1() {
| output1 = five() * four()
| output2 = 3 * three() * three()
| output3 = five() * three()
| }
|
| noinline void calc2() {
| output2 = 3 * three() * three()
| output1 = five() * four()
| output3 = three() * five()
| }
|
| noinline void calc3() {
| output2 = 3 * three() * three()
| output1 = four() * five()
| output3 = 3 * five()
| }
|
| noinline void crash_if_bad() {
| #if ARCH_6502
| if output1 != 20 { asm { lda $bfff }}
| if output2 != 27 { asm { lda $bfff }}
| if output3 != 15 { asm { lda $bfff }}
| #elseif ARCH_I80
| if output1 != 20 { asm { ld a,($bfff) }}
| if output2 != 27 { asm { ld a,($bfff) }}
| if output3 != 15 { asm { ld a,($bfff) }}
| #elseif ARCH_X86
| if output1 != 20 { asm { ld a,($bfff) }}
| if output2 != 27 { asm { ld a,($bfff) }}
| if output3 != 15 { asm { ld a,($bfff) }}
| #elseif ARCH_6809
| if output1 != 20 { asm { lda $bfff }}
| if output2 != 27 { asm { lda $bfff }}
| if output3 != 15 { asm { lda $bfff }}
| #else
| #error unsupported architecture
| #endif
| }
""".stripMargin){m =>
m.readByte(0xc003) should equal(15)
m.readByte(0xc002) should equal(27)
m.readByte(0xc001) should equal(20)
}
}
test("Byte multiplication 3") {
multiplyCase3(0, 0)
multiplyCase3(0, 1)
multiplyCase3(0, 2)
multiplyCase3(0, 5)
multiplyCase3(1, 0)
multiplyCase3(5, 0)
multiplyCase3(7, 0)
multiplyCase3(2, 5)
multiplyCase3(7, 2)
multiplyCase3(100, 2)
multiplyCase3(54, 4)
multiplyCase3(2, 100)
multiplyCase3(4, 54)
}
private def multiplyCase3(x: Int, y: Int): Unit = {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
s"""
| import zp_reg
| byte output @$$c000
| void main () {
| byte a
| a = f()
| output = a * g()
| }
| byte f() {return $x}
| byte g() {return $y}
""".
stripMargin)(_.readByte(0xc000) should equal(x * y) withClue s"$x * $y")
}
test("Byte division 1") {
// divisionCase1(0, 1)
// divisionCase1(1, 1)
// divisionCase1(2, 1)
// divisionCase1(250, 1)
// divisionCase1(0, 3)
// divisionCase1(0, 5)
divisionCase1(1, 5)
// divisionCase1(6, 5)
// divisionCase1(73, 5)
// divisionCase1(75, 5)
// divisionCase1(42, 11)
}
private def divisionCase1(x: Int, y: Int): Unit = {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
s"""
| import zp_reg
| byte output_q1 @$$c000, output_m1 @$$c001, output_q2 @$$c002, output_m2 @$$c003
| void main () {
| byte a
| a = f()
| output_q1 = a / $y
| output_m1 = a %% $y
| output_q2 = a
| output_m2 = a
| output_q2 /= $y
| output_m2 %%= $y
| }
| byte f() {return $x}
""".
stripMargin) { m =>
m.readByte(0xc000) should equal(x / y) withClue s"= $x / $y"
m.readByte(0xc001) should equal(x % y) withClue s"= $x %% $y"
m.readByte(0xc002) should equal(x / y) withClue s"= $x / $y"
m.readByte(0xc003) should equal(x % y) withClue s"= $x %% $y"
}
}
test("Byte division 2") {
divisionCase2(0, 1)
divisionCase2(1, 1)
divisionCase2(2, 1)
divisionCase2(250, 1)
divisionCase2(0, 3)
divisionCase2(0, 5)
divisionCase2(1, 5)
divisionCase2(6, 5)
divisionCase2(73, 5)
divisionCase2(75, 5)
divisionCase2(42, 11)
}
private def divisionCase2(x: Int, y: Int): Unit = {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
s"""
| import zp_reg
| byte output_q1 @$$c000, output_m1 @$$c001
| byte output_q2 @$$c002, output_m2 @$$c003
| void main () {
| byte a
| byte b
| a = f()
| b = g()
| output_q1 = a / b
| output_m1 = a %% b
| output_q2 = a
| output_m2 = a
| output_q2 /= b
| output_m2 %%= b
| }
| byte f() = $x
| noinline byte g() = $y
""".
stripMargin) { m =>
m.readByte(0xc000) should equal(x / y) withClue s"= $x / $y"
m.readByte(0xc001) should equal(x % y) withClue s"= $x %% $y"
m.readByte(0xc002) should equal(x / y) withClue s"= $x / $y"
m.readByte(0xc003) should equal(x % y) withClue s"= $x %% $y"
}
}
test("Byte division 3") {
divisionCase1(0, 2)
divisionCase1(1, 2)
divisionCase1(2, 2)
divisionCase1(250, 128)
divisionCase1(0, 4)
divisionCase1(0, 8)
divisionCase1(1, 4)
divisionCase1(6, 8)
divisionCase1(73, 16)
divisionCase1(75, 128)
divisionCase1(42, 128)
divisionCase1(142, 128)
}
test("Byte division 4") {
divisionCase4(0, 2)
divisionCase4(1, 2)
divisionCase4(2, 2)
divisionCase4(250, 128)
divisionCase4(0, 4)
divisionCase4(0, 8)
divisionCase4(1, 4)
divisionCase4(6, 8)
divisionCase4(73, 16)
divisionCase4(75, 128)
divisionCase4(42, 128)
divisionCase4(142, 128)
}
private def divisionCase4(x: Int, y: Int): Unit = {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)(
s"""
| import zp_reg
| byte output_q1 @$$c000, output_m1 @$$c001
| byte output_q2 @$$c002, output_m2 @$$c003
| void main () {
| byte a
| output_q2 = g()
| output_m2 = g()
| a = f()
| output_q1 = $x / a
| output_m1 = $x %% a
| output_q2 /= a
| output_m2 %%= a
| }
| byte f() {return $y}
| noinline byte g() {return $x}
""".
stripMargin) { m =>
m.readByte(0xc000) should equal(x / y) withClue s"= $x / $y"
m.readByte(0xc001) should equal(x % y) withClue s"= $x %% $y"
m.readByte(0xc002) should equal(x / y) withClue s"= $x / $y"
m.readByte(0xc003) should equal(x % y) withClue s"= $x %% $y"
}
}
test("Division bug repro"){
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(
s"""
| import zp_reg
| byte output_q1 @$$c000, output_m1 @$$c001
| array zeroes[256] = [for i,0,until,256 [0]]
| void main () {
| byte a
| byte b
| a = 186
| memory_barrier()
| a /= zeroes[b] | 2
| output_q1 = a
| }
""".
stripMargin) { m =>
m.readByte(0xc000) should equal(93)
}
}
test("Multiplication bug repro") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(
"""
| import zp_reg
| byte output @$c000
| array zeroes[256] = [for i,0,until,256 [0]]
| void main () {
| byte a,b
| a = 5
| b = 5
| memory_barrier()
| output = a * (b * a)
| }
""".
stripMargin) { m =>
m.readByte(0xc000) should equal(125)
}
}
test("Optimal multiplication detection") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(
"""
| import zp_reg
| word output @$c000
| noinline void run(byte a, byte b) {
| output = word(a) * b
| }
| void main () {
| run(100, 42)
| }
""".
stripMargin) { m =>
m.readWord(0xc000) should equal(4200)
}
}
}