1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-05-29 04:41:30 +00:00

65816: various improvements

This commit is contained in:
Karol Stasiak 2018-12-16 19:34:33 +01:00
parent cd8697552c
commit 5a99dc7293
14 changed files with 8564 additions and 36 deletions

1
.gitignore vendored
View File

@ -16,7 +16,6 @@ examples/lunix/
*.jar
*.class
*.zip
*.js
# compiled Millfork files
*.prg

View File

@ -152,6 +152,13 @@ object Opcode extends Enumeration {
case ASL => Some(ASL_W)
case LSR => Some(LSR_W)
case PHA => Some(PHA_W)
case PLA => Some(PLA_W)
case PHX => Some(PHX_W)
case PLX => Some(PLX_W)
case PHY => Some(PHY_W)
case PLY => Some(PLY_W)
case _ => None
}

View File

@ -14,20 +14,23 @@ object SixteenOptimizations {
val AccumulatorSwapping = new RuleBasedAssemblyOptimization("Accumulator swapping",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & HasOpcode(PHA) & HasAccu8 & DoesntMatterWhatItDoesWith(State.AH, State.A, State.N, State.Z)) ~
(Linear & Not(ConcernsStack)) ~
(Linear & Not(ConcernsStack)).* ~
(Elidable & HasOpcode(PLA) & DoesntMatterWhatItDoesWith(State.AH)) ~~> { code =>
AssemblyLine.implied(XBA) :: (code.tail.init :+ AssemblyLine.implied(XBA))
},
(Elidable & HasOpcode(TAX) & HasAccu8 & HasIndex8 & DoesntMatterWhatItDoesWith(State.AH, State.A, State.N, State.Z)) ~
(Linear & Not(ConcernsX)) ~
(Linear & Not(ConcernsX)).* ~
(Elidable & HasOpcode(TXA) & DoesntMatterWhatItDoesWith(State.AH, State.X)) ~~> { code =>
AssemblyLine.implied(XBA) :: (code.tail.init :+ AssemblyLine.implied(XBA))
},
(Elidable & HasOpcode(TAY) & HasAccu8 & HasIndex8 & DoesntMatterWhatItDoesWith(State.AH, State.A, State.N, State.Z)) ~
(Linear & Not(ConcernsY)) ~
(Linear & Not(ConcernsY)).* ~
(Elidable & HasOpcode(TYA) & DoesntMatterWhatItDoesWith(State.AH, State.Y)) ~~> { code =>
AssemblyLine.implied(XBA) :: (code.tail.init :+ AssemblyLine.implied(XBA))
},
(Elidable & HasOpcode(XBA) & DoesntMatterWhatItDoesWith(State.A, State.AH, State.N, State.Z)) ~~> { code =>
Nil
},
)
val RepSepWeakening = new RuleBasedAssemblyOptimization("REP/SEP weakening",
@ -121,6 +124,7 @@ object SixteenOptimizations {
val PointlessLoadAfterLoadOrStore = new RuleBasedAssemblyOptimization("Pointless 16-bit load after load or store",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
// TODO: flags!
(HasOpcodeIn(LDA_W, STA_W) & HasAddrMode(WordImmediate) & MatchParameter(1)) ~
(Linear & Not(ChangesA) & Not(ChangesAH)).* ~

View File

@ -45,7 +45,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
case 1 => List(
AssemblyLine(LDA, Immediate, expr.loByte),
AssemblyLine(STA, addrMode, addr))
case 2 => List(
case 2 => if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: AssemblyLine(LDA_W, WordImmediate, expr) ::(AssemblyLine.variable(ctx, STA_W, m) :+ AssemblyLine.accu8)
} else List(
AssemblyLine(LDA, Immediate, expr.loByte),
AssemblyLine(STA, addrMode, addr),
AssemblyLine(LDA, Immediate, expr.hiByte),
@ -54,13 +56,15 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
AssemblyLine(LDA, Immediate, expr.subbyte(i)),
AssemblyLine(STA, addrMode, addr + i))).flatten
}
case StackVariable(_, t, offset) =>
case m@StackVariable(_, t, offset) =>
t.size match {
case 0 => Nil
case 1 => AssemblyLine.tsx(ctx) ++ List(
AssemblyLine.immediate(LDA, expr.loByte),
AssemblyLine.dataStackX(ctx, STA, offset))
case 2 => AssemblyLine.tsx(ctx) ++ List(
case 2 => if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: AssemblyLine(LDA_W, WordImmediate, expr) :: (AssemblyLine.variable(ctx, STA_W, m) :+ AssemblyLine.accu8)
} else AssemblyLine.tsx(ctx) ++ List(
AssemblyLine.implied(TSX),
AssemblyLine.immediate(LDA, expr.loByte),
AssemblyLine.dataStackX(ctx, STA, offset),
@ -371,8 +375,11 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
AssemblyLine.implied(PLA))
} else List(AssemblyLine.immediate(LDX, 0), AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source) :+ AssemblyLine.immediate(LDX, 0)
case 2 =>
// TODO: use LDA_W
AssemblyLine.variable(ctx, LDA, source, 1) ++ List(AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source)
if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) :+ AssemblyLine.accu8)
} else {
AssemblyLine.variable(ctx, LDA, source, 1) ++ List(AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source)
}
}
case RegisterVariable(MosRegister.X, _) => AssemblyLine.variable(ctx, LDX, source)
case RegisterVariable(MosRegister.Y, _) => AssemblyLine.variable(ctx, LDY, source)
@ -422,6 +429,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
if (exprType.size > target.typ.size) {
ctx.log.error(s"Variable `$target.name` is too small", expr.position)
Nil
} else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8)
} else {
val copyFromLo = List.tabulate(exprType.size)(i => AssemblyLine.variable(ctx, LDA, source, i) ++ AssemblyLine.variable(ctx, STA, target, i))
val copy = if (shouldCopyFromHiToLo(source.toAddress, target.toAddress)) copyFromLo.reverse else copyFromLo
@ -437,6 +446,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
if (exprType.size > target.typ.size) {
ctx.log.error(s"Variable `$target.name` is too small", expr.position)
Nil
} else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8)
} else {
val copy = List.tabulate(exprType.size)(i => AssemblyLine.variable(ctx, LDA, source, i) :+ AssemblyLine.dataStackX(ctx, STA, target, i))
val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) {
@ -507,6 +518,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
if (exprType.size > target.typ.size) {
ctx.log.error(s"Variable `$target.name` is too small", expr.position)
Nil
} else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8)
} else {
val copy = List.tabulate(exprType.size)(i => AssemblyLine.dataStackX(ctx, LDA, offset + i) :: AssemblyLine.variable(ctx, STA, target, i))
val extend = if (exprType.size == target.typ.size) Nil else if (exprType.isSigned) {
@ -521,6 +534,8 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
if (exprType.size > target.typ.size) {
ctx.log.error(s"Variable `$target.name` is too small", expr.position)
Nil
} else if (exprType.size == 2 && target.typ.size == 2 && ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, LDA_W, source) ++ AssemblyLine.variable(ctx, STA_W, target) :+ AssemblyLine.accu8)
} else {
val copyFromLo = List.tabulate(exprType.size)(i => List(AssemblyLine.dataStackX(ctx, LDA, offset + i), AssemblyLine.dataStackX(ctx, STA, target, i)))
val copy = if (shouldCopyFromHiToLo(NumericConstant(source.baseOffset, 2), NumericConstant(target.baseOffset, 2))) copyFromLo.reverse else copyFromLo
@ -662,9 +677,15 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
calculate ++ store
}
case 2 =>
val calculate = PseudoregisterBuiltIns.compileWordAdditionToAX(ctx, params, decimal = decimal)
val store = expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
calculate ++ store
if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
val calculate = PseudoregisterBuiltIns.compileWordAdditionToAW(ctx, params, decimal = decimal)
val store = expressionStorageFromAW(ctx, exprTypeAndVariable, expr.position)
calculate ++ store
} else {
val calculate = PseudoregisterBuiltIns.compileWordAdditionToAX(ctx, params, decimal = decimal)
val store = expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
calculate ++ store
}
}
}
case SeparateBytesExpression(h, l) =>
@ -1302,6 +1323,85 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
}
}
def expressionStorageFromAW(ctx: CompilationContext, exprTypeAndVariable: Option[(Type, Variable)], position: Option[Position]): List[AssemblyLine] = {
exprTypeAndVariable.fold(noop) {
case (VoidType, _) => ctx.log.fatal("Cannot assign word to void", position)
case (_, RegisterVariable(MosRegister.A, _)) => noop
case (_, RegisterVariable(MosRegister.AW, _)) => noop
case (_, RegisterVariable(MosRegister.X, _)) => List(AssemblyLine.implied(TAX))
case (_, RegisterVariable(MosRegister.Y, _)) => List(AssemblyLine.implied(TAY))
case (_, RegisterVariable(MosRegister.AX, _)) =>
// TODO: sign extension
List(AssemblyLine.implied(XBA), AssemblyLine.implied(TAX), AssemblyLine.implied(XBA))
case (_, RegisterVariable(MosRegister.XA, _)) =>
// TODO: sign extension
List(AssemblyLine.implied(TAX), AssemblyLine.implied(XBA))
case (_, RegisterVariable(MosRegister.YA, _)) =>
// TODO: sign extension
List(AssemblyLine.implied(TAY), AssemblyLine.implied(XBA))
case (_, RegisterVariable(MosRegister.AY, _)) =>
// TODO: sign extension
List(AssemblyLine.implied(XBA), AssemblyLine.implied(TAY), AssemblyLine.implied(XBA))
case (t, v: VariableInMemory) => t.size match {
case 1 => v.typ.size match {
case 1 =>
AssemblyLine.variable(ctx, STA, v)
case s if s > 1 =>
if (t.isSigned) {
AssemblyLine.variable(ctx, STA, v) ++ signExtendA(ctx) ++ List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten
} else {
AssemblyLine.variable(ctx, STA, v) ++ List(AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.variable(ctx, STA, v, i + 1)).flatten
}
}
case 2 => v.typ.size match {
case 1 =>
ctx.log.error(s"Variable `${v.name}` cannot hold a word", position)
Nil
case 2 =>
AssemblyLine.accu16 :: (AssemblyLine.variable(ctx, STA_W, v) :+ AssemblyLine.accu8)
case s if s > 2 =>
if (t.isSigned) {
AssemblyLine.accu16 :: AssemblyLine.variable(ctx, STA_W, v) ++
List(AssemblyLine.accu8, AssemblyLine.implied(XBA)) ++
signExtendA(ctx) ++
List.tabulate(s - 2)(i => AssemblyLine.variable(ctx, STA, v, i + 2)).flatten
} else {
AssemblyLine.accu16 :: AssemblyLine.variable(ctx, STA_W, v)
List(AssemblyLine.accu8, AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 2)(i => AssemblyLine.variable(ctx, STA, v, i + 2)).flatten
}
}
}
case (t, v: StackVariable) => t.size match {
case 1 => v.typ.size match {
case 1 =>
AssemblyLine.tsx(ctx) :+ AssemblyLine.dataStackX(ctx, STA, v)
case s if s > 1 =>
AssemblyLine.tsx(ctx) ++ (if (t.isSigned) {
List(
AssemblyLine.dataStackX(ctx, STA, v.baseOffset)) ++
signExtendA(ctx) ++
List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1))
} else {
List(
AssemblyLine.dataStackX(ctx, STA, v.baseOffset),
AssemblyLine.immediate(LDA, 0)) ++
List.tabulate(s - 1)(i => AssemblyLine.dataStackX(ctx, STA, v, i + 1))
})
}
case 2 => v.typ.size match {
case 1 =>
ctx.log.error(s"Variable `${v.name}` cannot hold a word", position)
Nil
case 2 =>
AssemblyLine.tsx(ctx) ++ List(AssemblyLine.accu16, AssemblyLine.dataStackX(ctx, STA_W, v), AssemblyLine.accu8)
case s if s > 2 => ???
}
}
}
}
def compileAssignment(ctx: CompilationContext, source: Expression, target: LhsExpression): List[AssemblyLine] = {
val env = ctx.env
val sourceType = AbstractExpressionCompiler.checkAssignmentTypeAndGetSourceType(ctx, source, target)

View File

@ -45,7 +45,7 @@ object PseudoregisterBuiltIns {
if (params.isEmpty) {
return List(AssemblyLine.immediate(LDA, 0), AssemblyLine.immediate(LDX, 0))
}
val reg = ctx.env.get[VariableInMemory]("__reg")
val reg = ctx.env.get[VariableInMemory]("__reg.loword")
val head = params.head match {
case (false, e) => MosExpressionCompiler.compile(ctx, e, Some(MosExpressionCompiler.getExpressionType(ctx, e) -> reg), BranchSpec.None)
case (true, e) => ???
@ -56,6 +56,37 @@ object PseudoregisterBuiltIns {
)
}
def compileWordAdditionToAW(ctx: CompilationContext, params: List[(Boolean, Expression)], decimal: Boolean): List[AssemblyLine] = {
// assume native16 is enabled and accu16 mode is disabled
val b = ctx.env.get[Type]("byte")
val w = ctx.env.get[Type]("word")
if (!decimal) {
val (variablePart, constPart) = ctx.env.evalVariableAndConstantSubParts(SumExpression(params, decimal = false))
variablePart match {
case None =>
return MosExpressionCompiler.compileConstant(ctx, constPart, RegisterVariable(MosRegister.AW, w))
case Some(v) =>
}
}
if (ctx.options.zpRegisterSize < 2) {
ctx.log.error("Word addition or subtraction requires the zeropage pseudoregister", params.headOption.flatMap(_._2.position))
return Nil
}
if (params.isEmpty) {
return List(AssemblyLine.accu16, AssemblyLine.immediate(LDA_W, 0), AssemblyLine.accu8)
}
val reg = ctx.env.get[VariableInMemory]("__reg.loword")
val head = params.head match {
case (false, e) => MosExpressionCompiler.compile(ctx, e, Some(MosExpressionCompiler.getExpressionType(ctx, e) -> reg), BranchSpec.None)
case (true, e) => ???
}
params.tail.foldLeft[List[AssemblyLine]](head){case (code, (sub, param)) => code ++ addToReg(ctx, param, sub, decimal)} ++ List(
AssemblyLine.accu16,
AssemblyLine.zeropage(LDA_W, reg),
AssemblyLine.accu8
)
}
def addToReg(ctx: CompilationContext, r: Expression, subtract: Boolean, decimal: Boolean): List[AssemblyLine] = {
if (ctx.options.zpRegisterSize < 2) {
ctx.log.error("Word addition or subtraction requires the zeropage pseudoregister", r.position)
@ -65,11 +96,13 @@ object PseudoregisterBuiltIns {
ctx.log.error("Unsupported decimal operation. Consider increasing the size of the zeropage register.", r.position)
return Nil
}
val native16 = ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)
val b = ctx.env.get[Type]("byte")
val w = ctx.env.get[Type]("word")
val reg = ctx.env.get[VariableInMemory]("__reg.loword")
// TODO: smarter on 65816
val op = if (subtract) SBC else ADC
val op16 = if (subtract) SBC_W else ADC_W
val prepareCarry = AssemblyLine.implied(if (subtract) SEC else CLC)
val rightType = MosExpressionCompiler.getExpressionType(ctx, r)
if (decimal && !ctx.options.flag(CompilationFlag.DecimalMode)) {
@ -112,6 +145,11 @@ object PseudoregisterBuiltIns {
AssemblyLine0(STA, ZeroPage, _),
AssemblyLine0(LDX | LDA, Immediate, NumericConstant(0, _)),
AssemblyLine0(STA | STX, ZeroPage, _)) => Nil
case List(
AssemblyLine0(REP, Immediate, NumericConstant(0x20, _)),
AssemblyLine0(LDA_W, WordImmediate, NumericConstant(0, _)),
AssemblyLine0(STA_W, ZeroPage, _),
AssemblyLine0(SEP, Immediate, NumericConstant(0x20, _))) => Nil
case List(
l@AssemblyLine0(LDA, _, _),
@ -126,9 +164,25 @@ object PseudoregisterBuiltIns {
h.copy(opcode = op),
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.zeropage(LDA, reg)))
case List(
AssemblyLine0(REP, Immediate, NumericConstant(0x20, _)),
l@AssemblyLine0(LDA_W, addrMode, _),
AssemblyLine0(STA_W, ZeroPage, _),
AssemblyLine0(SEP, Immediate, NumericConstant(0x20, _))) if addrMode != ZeroPageY =>
BuiltIns.wrapInSedCldIfNeeded(decimal, List(
AssemblyLine.accu16,
prepareCarry,
AssemblyLine.zeropage(LDA_W, reg),
l.copy(opcode = op16),
AssemblyLine.zeropage(STA_W, reg),
AssemblyLine.accu8))
case _ =>
List(
if (native16) {
List(AssemblyLine.accu16, AssemblyLine.zeropage(LDA_W, reg), AssemblyLine.implied(PHA_W), AssemblyLine.accu8) ++
MosExpressionCompiler.fixTsx(MosExpressionCompiler.fixTsx(compileRight)) ++
List(AssemblyLine.accu16, prepareCarry, AssemblyLine.implied(PLA_W), AssemblyLine.zeropage(op16, reg), AssemblyLine.zeropage(STA_W, reg), AssemblyLine.accu8)
} else List(
AssemblyLine.zeropage(LDA, reg, 1),
AssemblyLine.implied(PHA),
AssemblyLine.zeropage(LDA, reg),

4097
src/test/resources/cpu.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ import org.scalatest.{FunSuite, Matchers}
class WordMathSuite extends FunSuite with Matchers {
test("Word addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| word a
| void main () {
@ -21,7 +21,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Cast word addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen/*, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp*/)("""
| byte output @$c000
| word a
| void main () {
@ -34,7 +34,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word subtraction") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| word a
| void main () {
@ -46,7 +46,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word subtraction 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| word a
| void main () {
@ -58,7 +58,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word subtraction 3") {
EmuCrossPlatformBenchmarkRun(Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen/*, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp*/)("""
| word output @$c000
| word a
| void main () {
@ -73,7 +73,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Byte-to-word addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| word pair
| void main () {
@ -86,7 +86,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Literal addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| output = 640
@ -96,7 +96,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Array element addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| word pair
| array b[2]
@ -113,7 +113,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("nesdev.com example") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| byte output @$c000
| byte tile @$C3A6
| array map [256] @$c300
@ -135,7 +135,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("nesdev.com example 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| byte output @$c000
| byte tile @$C3A6
| array map [256] @$c300
@ -153,7 +153,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("hi()/lo()") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| array output [7] @$c000
| void main () {
| output[0] = lo(33)
@ -185,7 +185,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word addition 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen/*, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp*/)("""
| word output @$c000
| void main () {
| word v
@ -201,7 +201,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word addition 3") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| byte c
@ -217,7 +217,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word addition 4") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| word v
@ -237,7 +237,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word bit ops 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| word v
@ -253,7 +253,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word shift") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| word v
@ -271,7 +271,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word shift 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| output = five()
@ -286,7 +286,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word shift 3") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| output = five()
@ -301,7 +301,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word shift 4") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| output = five()
@ -316,7 +316,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word shift 5") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| output = five()
@ -332,7 +332,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word multiplication 5") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| output = alot()
@ -369,7 +369,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
private def multiplyCase1(x: Int, y: Int): Unit = {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)(
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80)(
s"""
| import zp_reg
| word output @$$c000

View File

@ -123,6 +123,9 @@ object EmuCrossPlatformBenchmarkRun {
if (platforms.contains(millfork.Cpu.Cmos)) {
EmuCmosBenchmarkRun.apply(source)(verifier)
}
if (platforms.contains(millfork.Cpu.Sixteen)) {
EmuNative65816BenchmarkRun.apply(source)(verifier)
}
if (platforms.contains(millfork.Cpu.Z80)) {
EmuZ80BenchmarkRun.apply(source)(verifier)
}

View File

@ -0,0 +1,22 @@
package millfork.test.emu
import millfork.output.MemoryBank
/**
* @author Karol Stasiak
*/
object EmuNative65816BenchmarkRun {
def apply(source:String)(verifier: MemoryBank=>Unit): Unit = {
println(f"Compiling for 65816 (unoptimized)")
val (Timings(_, t0), m0) = EmuUnoptimizedNative65816Run.apply2(source)
println(f"Compiling for 65816 (optimized)")
val (Timings(_, t2), m2) = EmuOptimizedNative65816Run.apply2(source)
println(f"Before optimization: $t0%7d")
println(f"After optimization: $t2%7d")
println(f"Gain: ${(100L*(t0-t2)/t0.toDouble).round}%7d%%")
println(f"Running 65816 unoptimized")
verifier(m0)
println(f"Running 65816 optimized")
verifier(m2)
}
}

View File

@ -16,5 +16,17 @@ object EmuOptimized65816Run extends EmuRun(
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good)
object EmuOptimizedNative65816Run extends EmuRun(
Cpu.Sixteen,
OptimizationPresets.NodeOpt,
OptimizationPresets.AssOpt ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good) {
override def native16: Boolean = true
}

View File

@ -65,6 +65,8 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
def softwareStack = false
def native16 = false
private val timingNmos = Array[Int](
7, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6,
2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
@ -136,6 +138,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
CompilationFlag.SoftwareStack -> softwareStack,
CompilationFlag.EmitCmosOpcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu),
CompilationFlag.EmitEmulation65816Opcodes -> (platform.cpu == millfork.Cpu.Sixteen),
CompilationFlag.EmitNative65816Opcodes -> (platform.cpu == millfork.Cpu.Sixteen && native16),
CompilationFlag.Emit65CE02Opcodes -> (platform.cpu == millfork.Cpu.CE02),
CompilationFlag.EmitHudsonOpcodes -> (platform.cpu == millfork.Cpu.HuC6280),
CompilationFlag.OptimizeForSize -> optimizeForSize,
@ -145,8 +148,18 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
), None, 4, Map(), JobContext(log, new LabelGenerator))
log.hasErrors = false
log.verbosity = 999
if (native16 && platform.cpu != millfork.Cpu.Sixteen) throw new IllegalStateException
var effectiveSource = source
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
if (native16) effectiveSource +=
"""
|
|asm void __init_16bit() @$200 {
| clc
| xce
| sep #$30
|}
""".stripMargin
log.setSource(Some(effectiveSource.lines.toIndexedSeq))
val PreprocessingResult(preprocessedSource, features, _) = Preprocessor.preprocessForTest(options, effectiveSource)
val parserF = MosParser("", preprocessedSource, "", options, features)
@ -216,6 +229,8 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
val timings = platform.cpu match {
case millfork.Cpu.Cmos =>
runViaSymon(log, memoryBank, platform.codeAllocators("default").startAt, CpuBehavior.CMOS_6502)
case millfork.Cpu.Sixteen =>
runViaJs(log, memoryBank, platform.codeAllocators("default").startAt)
case millfork.Cpu.Ricoh =>
runViaHalfnes(log, memoryBank, platform.codeAllocators("default").startAt)
case millfork.Cpu.Mos =>
@ -300,4 +315,9 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
Timings(countNmos, countCmos) -> memoryBank
}
def runViaJs(log: Logger, memoryBank: MemoryBank, org: Int): (Timings, MemoryBank) = {
val (cycles, newOutput) = NashornEmulator.run(memoryBank.output, 80, 0x200)
System.arraycopy(newOutput, 0, memoryBank.output, 0, 1 << 16)
Timings(cycles, cycles) -> memoryBank
}
}

View File

@ -12,6 +12,10 @@ object EmuUnoptimizedRicohRun extends EmuRun(Cpu.Ricoh, Nil, Nil)
object EmuUnoptimizedCmosRun extends EmuRun(Cpu.Cmos, Nil, Nil)
object EmuUnoptimizedNative65816Run extends EmuRun(Cpu.Sixteen, Nil, Nil) {
override def native16: Boolean = true
}
object EmuUnoptimizedZ80Run extends EmuZ80Run(Cpu.Z80, Nil, Nil)
object EmuUnoptimizedIntel8080Run extends EmuZ80Run(Cpu.Intel8080, Nil, Nil)

View File

@ -0,0 +1,111 @@
package millfork.test.emu
import javax.script.{Bindings, ScriptEngine, ScriptEngineManager}
import java.io.FileReader
import java.nio.file.Paths
import jdk.nashorn.api.scripting.ScriptObjectMirror
import scala.language.dynamics
/**
* @author Karol Stasiak
*/
object NashornEmulator {
lazy val engine = {
val jsFile = Paths.get(classOf[Nothing].getResource("/cpu.js").toURI).toFile
val engine: ScriptEngine = new ScriptEngineManager().getEngineByName("nashorn")
engine.eval(new FileReader(jsFile))
engine
}
private def newCpu(): JsObject = {
JsObject(engine.get("CPU_65816").asInstanceOf[ScriptObjectMirror]).construct()
}
def run(memory: Array[Byte], steps: Long, start: Int): (Long, Array[Byte]) = {
val hex = memory.map(b => f"${b&0xff}%02x").mkString("")
val cpu = newCpu()
cpu.reset()
cpu.load_binary(memory.map(_ & 0xff), 0)
val memory0 = cpu.mmu.memory.!("0")
for (i <- 0 until 1 << 16) memory0.!(i.toString, memory(i) & 0xff)
cpu.r.pc = start
cpu.r.k = 0
var count = 0L
while (count < steps && cpu.r.s.*[Number].intValue().&(0xff) > 1) {
cpu.step()
count += 1
}
val newMemory = (0 until 1 << 16).map(i => memory0.!(i.toString).*[Number].byteValue()).toArray
val cycles = cpu.cycle_count.*[Number].longValue()
cycles -> newMemory
}
def main(args: Array[String]): Unit = {
val cpu = newCpu()
cpu.reset()
cpu.load_binary("00", 0x200)
cpu.execute(0x200)
println(cpu.r.pc)
}
}
case class JsObject(private val mirror: Any) extends Dynamic {
def !(index: JsObject): JsObject =
mirror match {
case x: ScriptObjectMirror => JsObject(x.get(index.mirror))
case _ => throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass")
}
def !(index: Any): JsObject =
mirror match {
case x: ScriptObjectMirror => JsObject(x.get(index))
case _ => throw new IllegalArgumentException(s"Accessing field $index of $getUnderlyingClass")
}
def !(index: String, value:Any): Unit =
mirror match {
case x: ScriptObjectMirror => x.setMember(index, value)
case _ => throw new IllegalArgumentException(s"Setting field $index of $getUnderlyingClass")
}
def selectDynamic(name: String): JsObject =
mirror match {
case x: ScriptObjectMirror => JsObject(x.get(name).asInstanceOf[Any] match {
case y: Double => if (y.isValidInt) y.toInt else y
case y => y
})
case _ => throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass")
}
private def getUnderlyingClass = {
if (mirror.asInstanceOf[AnyRef] eq null) "null" else mirror.getClass.getSimpleName
}
def updateDynamic(name: String)(value: Any): Unit =
mirror match {
case x: ScriptObjectMirror => x.setMember(name, value)
case _ => throw new IllegalArgumentException(s"Setting field $name of $getUnderlyingClass")
}
def applyDynamic(name: String)(params: Any*): JsObject =
mirror match {
case x: ScriptObjectMirror => JsObject(x.callMember(name, params.toArray))
case _ => throw new IllegalArgumentException(s"Accessing field $name of $getUnderlyingClass")
}
def construct(params: Any*): JsObject =
mirror match {
case x: ScriptObjectMirror => JsObject(x.newObject(params.toArray))
case _ => throw new IllegalArgumentException(s"Using $getUnderlyingClass as a constructor")
}
@inline
def *[T]: T = mirror.asInstanceOf[T]
override def toString: String = String.valueOf(mirror)
}