1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-12 06:29:34 +00:00

Allow taking addresses of stack variables

This commit is contained in:
Karol Stasiak 2019-06-23 22:53:42 +02:00
parent f22b62e57f
commit 0205520bf9
11 changed files with 246 additions and 22 deletions

View File

@ -30,6 +30,8 @@
* Added `nullptr`.
* You can now take a pointer to a stack variable.
* Arrays can now have elements of types other than `byte` (still limited in size to 1 byte though) and be built out of struct literals.
* Arrays can now be constant.
@ -64,6 +66,8 @@
* 8080 and LR35902: fixed large stack variables.
* Other bug fixes.
* Optimization improvements.
## 0.3.2

View File

@ -42,7 +42,7 @@ but the main disadvantages are:
* increased stack usage
* cannot take their addresses
* their addresses are not considered constants and it's slower to get them
* cannot use them in inline assembly code blocks

View File

@ -1001,7 +1001,7 @@ object AlwaysGoodOptimizations {
XContainsHardwareStackPointer & HasOpcodeIn(INC, DEC, ASL, LSR) & MatchAddrMode(0) & MatchParameter(1) |
Linear & XContainsHardwareStackPointer & HasAddrMode(AbsoluteX) & Not(MatchParameter(1)) & Not(HasOpcodeIn(OpcodeClasses.AccessesWordInMemory)) |
Linear & HasAddrMode(AbsoluteX) & Not(MatchParameter(1) & XContainsHardwareStackPointer) & Not(ChangesMemory) |
Linear & Not(ChangesS) & DoesntChangeMemoryAt(0, 1) & Not(HasAddrMode(AbsoluteX))).* ~
Linear & Not(ChangesS) & DoesntChangeMemoryAt(0, 1) & Not(HasAddrMode(AbsoluteX)) & Not(HasOpcodeIn(TXA, TXY, HuSAX, SXY, STX, STX_W, PHX, PHX_W, SAX, XAA, SAX, AHX, SHX, TAS))).* ~
(Elidable & XContainsHardwareStackPointer & HasOpcodeIn(LDA, LDX, LDY, ADC, SBC, ORA, EOR, AND, CMP) & MatchAddrMode(0) & MatchParameter(1)) ~~> { (code, ctx) =>
val oldA = ctx.get[Int](2)
val ADDR = ctx.get[Constant](1)

View File

@ -28,6 +28,13 @@ object CoarseFlowAnalyzer {
val preservesE: Set[String] = Set("__divmod_u16u8u16u8")
val preservesH: Set[String] = Set("__mul_u8u8u8")
val preservesL: Set[String] = Set("__mul_u8u8u8")
// does the code preserve the stack variables apart from direct writes, assuming Z80 and base pointer in IX?
val preservesStackVariables = code.forall {
case ZLine0(ZOpcode.ADD_16, TwoRegisters(ZRegister.HL, ZRegister.SP), _) => false
case ZLine0(ZOpcode.ADD_16, TwoRegisters(ZRegister.IY, ZRegister.SP), _) => false
case ZLine0(ZOpcode.ADD_16, TwoRegisters(ZRegister.IX, ZRegister.SP), _) => compilationOptions.flag(CompilationFlag.UseIxForStack)
case _ => true
}
var changed = true
while (changed) {
@ -56,7 +63,7 @@ object CoarseFlowAnalyzer {
case _ => true
}
if (mayBeCalled) {
val result = initialStatus.copy(memIx = currentStatus.memIx)
val result = if (preservesStackVariables) initialStatus.copy(memIx = currentStatus.memIx) else initialStatus
currentStatus = result.copy(
b = if (preservesB(n)) currentStatus.b else result.b,
c = if (preservesC(n)) currentStatus.c else result.c,
@ -144,19 +151,21 @@ object CoarseFlowAnalyzer {
val newV = currentStatus.getRegister(r).map(i => i.+(1).&(0xff))
currentStatus = currentStatus.
copy(cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus).
setRegister(r, newV)
setRegister(r, newV).
cleanMemIxIfNeeded(preservesStackVariables, r)
case ZLine0(DEC, OneRegister(r), _) =>
val newV = currentStatus.getRegister(r).map(i => i.-(1).&(0xff))
currentStatus = currentStatus.
copy(cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus).
setRegister(r, newV)
setRegister(r, newV).
cleanMemIxIfNeeded(preservesStackVariables, r)
case ZLine0(INC, OneRegisterOffset(r, o), _) =>
val newV = currentStatus.getRegister(r, o).map(i => i.+(1).&(0xff))
val newV = currentStatus.cleanMemIxIfNeeded(preservesStackVariables, r).getRegister(r, o).map(i => i.+(1).&(0xff))
currentStatus = currentStatus.
copy(cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus).
setRegister(r, newV, o)
case ZLine0(DEC, OneRegisterOffset(r, o), _) =>
val newV = currentStatus.getRegister(r, o).map(i => i.-(1).&(0xff))
val newV = currentStatus.cleanMemIxIfNeeded(preservesStackVariables, r).getRegister(r, o).map(i => i.-(1).&(0xff))
currentStatus = currentStatus.
copy(cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus).
setRegister(r, newV, o)
@ -174,21 +183,27 @@ object CoarseFlowAnalyzer {
case ZLine0(LD_AHLI, _, _) =>
val newHL = currentStatus.getRegister(ZRegister.HL).map(i => i.+(1).&(0xffff))
currentStatus = currentStatus.copy(a = AnyStatus).setRegister(ZRegister.HL, newHL)
currentStatus = currentStatus.copy(a = AnyStatus).setRegister(ZRegister.HL, newHL).
cleanMemIxIfNeeded(preservesStackVariables, ZRegister.MEM_HL)
case ZLine0(LD_HLIA, _, _) =>
val newHL = currentStatus.getRegister(ZRegister.HL).map(i => i.+(1).&(0xffff))
currentStatus = currentStatus.setRegister(ZRegister.HL, newHL)
currentStatus = currentStatus.setRegister(ZRegister.HL, newHL).
cleanMemIxIfNeeded(preservesStackVariables, ZRegister.MEM_HL)
case ZLine0(LD_AHLD, _, _) =>
val newHL = currentStatus.getRegister(ZRegister.HL).map(i => i.-(1).&(0xffff))
currentStatus = currentStatus.copy(a = AnyStatus).setRegister(ZRegister.HL, newHL)
currentStatus = currentStatus.copy(a = AnyStatus).setRegister(ZRegister.HL, newHL).
cleanMemIxIfNeeded(preservesStackVariables, ZRegister.MEM_HL)
case ZLine0(LD_HLDA, _, _) =>
val newHL = currentStatus.getRegister(ZRegister.HL).map(i => i.-(1).&(0xffff))
currentStatus = currentStatus.setRegister(ZRegister.HL, newHL)
currentStatus = currentStatus.setRegister(ZRegister.HL, newHL).
cleanMemIxIfNeeded(preservesStackVariables, ZRegister.MEM_HL)
case ZLine0(op, OneRegister(r), _) if ZOpcodeClasses.SET(op) =>
currentStatus = currentStatus.setRegister(r, currentStatus.getRegister(r).map(i => i | 1.<<(ZOpcodeClasses.SET_seq.indexOf(op))))
currentStatus = currentStatus.setRegister(r, currentStatus.getRegister(r).map(i => i | 1.<<(ZOpcodeClasses.SET_seq.indexOf(op)))).
cleanMemIxIfNeeded(preservesStackVariables, r)
case ZLine0(op, OneRegister(r), _) if ZOpcodeClasses.RES(op) =>
currentStatus = currentStatus.setRegister(r, currentStatus.getRegister(r).map(i => i & ~1.<<(ZOpcodeClasses.RES_seq.indexOf(op))))
currentStatus = currentStatus.setRegister(r, currentStatus.getRegister(r).map(i => i & ~1.<<(ZOpcodeClasses.RES_seq.indexOf(op)))).
cleanMemIxIfNeeded(preservesStackVariables, r)
case l@ZLine0(ADD, OneRegisterOffset(s, o), _) =>
val (newA, newC) = currentStatus.a.adc(currentStatus.getRegister(s, o), Status.SingleFalse)
@ -221,11 +236,12 @@ object CoarseFlowAnalyzer {
case ZLine0(LD, TwoRegisters(ZRegister.A, ZRegister.I | ZRegister.R), _) =>
currentStatus = currentStatus.copy(a = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus, nf = AnyStatus)
case ZLine0(LD, TwoRegisters(t, ZRegister.IMM_8), NumericConstant(value, _)) =>
currentStatus = currentStatus.setRegister(t, SingleStatus(value.toInt))
currentStatus = currentStatus.setRegister(t, SingleStatus(value.toInt)).
cleanMemIxIfNeeded(preservesStackVariables, t)
case ZLine0(LD, TwoRegistersOffset(t, ZRegister.IMM_8, o), NumericConstant(value, _)) =>
currentStatus = currentStatus.setRegister(t, SingleStatus(value.toInt), o)
currentStatus = currentStatus.cleanMemIxIfNeeded(preservesStackVariables, t).setRegister(t, SingleStatus(value.toInt), o)
case ZLine0(LD | LD_16, TwoRegisters(t, s), _) =>
currentStatus = currentStatus.setRegister(t, currentStatus.getRegister(s))
currentStatus = currentStatus.setRegister(t, currentStatus.getRegister(s)).cleanMemIxIfNeeded(preservesStackVariables, t)
case ZLine0(LD | LD_16, TwoRegistersOffset(t, s, o), _) =>
currentStatus = currentStatus.setRegister(t, currentStatus.getRegister(s, o), o)
case ZLine0(EX_DE_HL, _, _) =>
@ -237,9 +253,11 @@ object CoarseFlowAnalyzer {
case ZLine0(SLA, OneRegister(r), _) =>
currentStatus = currentStatus.copy(cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus)
.setRegister(r, currentStatus.getRegister(r).map(_.<<(1).&(0xff)))
.cleanMemIxIfNeeded(preservesStackVariables, r)
case ZLine0(SRL, OneRegister(r), _) =>
currentStatus = currentStatus.copy(cf = AnyStatus, zf = AnyStatus, sf = AnyStatus, pf = AnyStatus, hf = AnyStatus)
.setRegister(r, currentStatus.getRegister(r).map(_.>>(1).&(0x7f)))
.cleanMemIxIfNeeded(preservesStackVariables, r)
case ZLine0(RLA | RRA | RLCA | RRCA, _, _) =>

View File

@ -144,6 +144,12 @@ case class CpuStatus(a: Status[Int] = UnknownStatus,
case UnknownStatus => this.copy(l = UnknownStatus, h = UnknownStatus, hl = UnknownStatus)
}
def cleanMemIxIfNeeded(preservesStackVariables: Boolean, r: ZRegister.Value): CpuStatus = if (preservesStackVariables) this else {
r match {
case ZRegister.MEM_HL | ZRegister.MEM_BC | ZRegister.MEM_DE => copy(memIx = Map())
case _ => this
}
}
override def toString: String = {
val memRepr = if (memIx.isEmpty) "" else (0 to memIx.keys.max).map(i => memIx.getOrElse(i, UnknownStatus)).mkString("")

View File

@ -41,7 +41,7 @@ object BuiltIns {
val parts: (List[AssemblyLine], List[AssemblyLine]) = env.eval(source).fold {
val b = env.get[Type]("byte")
source match {
case VariableExpression(name) =>
case VariableExpression(name) if env.maybeGet[Variable](name).isDefined =>
val v = env.get[Variable](name)
if (v.typ.size > 1) {
ctx.log.error(s"Variable `$name` is too big for a built-in operation", source.position)

View File

@ -447,6 +447,67 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
compileToZReg(ctx, pointerExpression) -> ctx.env.get[ThingInMemory]("__reg.loword")
}
def compileStackOffset(ctx: CompilationContext, target: Variable, offset: Int, subbyte: Option[Int]): List[AssemblyLine] = {
val hi = if (ctx.options.flag(CompilationFlag.SoftwareStack)) {
MemoryAddressConstant(ctx.env.get[ThingInMemory]("__stack")).hiByte
} else {
Constant.One
}
val tsx = if (ctx.options.flag(CompilationFlag.SoftwareStack)) {
AssemblyLine.absolute(LDX, MemoryAddressConstant(ctx.env.get[ThingInMemory]("__sp")))
} else {
AssemblyLine.implied(TSX)
}
val actualOffset = if (ctx.options.flag(CompilationFlag.SoftwareStack)) {
offset & 0xff
} else {
(offset + ctx.extraStackOffset) & 0xff
}
val b = ctx.env.get[Type]("byte")
subbyte match {
case Some(1) => compile(ctx, GeneratedConstantExpression(hi, b), Some(b -> target), BranchSpec.None)
case Some(0) => target match {
case RegisterVariable(MosRegister.A, _) => actualOffset match {
case 0 => List(tsx, AssemblyLine.implied(TXA))
case 1 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(TXA))
case 2 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(INX), AssemblyLine.implied(TXA))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset))
}
case RegisterVariable(MosRegister.X, _) => actualOffset match {
case 0 => List(tsx)
case 1 => List(tsx, AssemblyLine.implied(INX))
case 2 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(INX))
case 3 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(INX), AssemblyLine.implied(INX))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset), AssemblyLine.implied(TAX))
}
case RegisterVariable(MosRegister.Y, _) => actualOffset match {
case 0 => List(tsx)
case 1 => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(TAY), AssemblyLine.implied(INY))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset), AssemblyLine.implied(TAY))
}
case _ =>
val loadA = actualOffset match {
case 0 => List(tsx, AssemblyLine.implied(TXA))
case 1 => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(INX))
case 2 => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(INX), AssemblyLine.implied(INX))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset))
}
loadA ++ expressionStorageFromA(ctx, Some(b -> target), None)
}
case None =>
val loadA = actualOffset match {
case 0 => List(tsx, AssemblyLine.implied(TXA))
case 1 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(TXA))
case 2 => List(tsx, AssemblyLine.implied(INX), AssemblyLine.implied(INX), AssemblyLine.implied(TXA))
case _ => List(tsx, AssemblyLine.implied(TXA), AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, actualOffset))
}
loadA ++ List(AssemblyLine.immediate(LDX, hi)) ++ expressionStorageFromAX(ctx, Some(ctx.env.get[Type]("pointer") -> target), None)
case _ => throw new IllegalArgumentException
}
}
def compile(ctx: CompilationContext, expr: Expression, exprTypeAndVariable: Option[(Type, Variable)], branches: BranchSpec): List[AssemblyLine] = {
val env = ctx.env
env.eval(expr) match {
@ -476,6 +537,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
assertCompatible(exprType, target.typ)
env.eval(expr).map(c => compileConstant(ctx, c, target)).getOrElse {
env.get[TypedThing](name) match {
case source: StackOffsetThing => compileStackOffset(ctx, target, source.offset, source.subbyte)
case source: VariableInMemory =>
target match {
case RegisterVariable(MosRegister.A, _) => AssemblyLine.variable(ctx, LDA, source)

View File

@ -253,7 +253,14 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case LiteralExpression(value, _) => ???
case GeneratedConstantExpression(_, _) => ???
case VariableExpression(name) =>
env.get[Variable](name) match {
env.get[VariableLikeThing](name) match {
case s: StackOffsetThing =>
s.subbyte match {
case None => targetifyHL(ctx, target, calculateStackAddressToHL(ctx, s.offset))
case Some(0) => targetifyA(ctx, target, calculateStackAddressToHL(ctx, s.offset) :+ ZLine.ld8(ZRegister.A, ZRegister.L), isSigned = false)
case Some(1) => targetifyA(ctx, target, calculateStackAddressToHL(ctx, s.offset) :+ ZLine.ld8(ZRegister.A, ZRegister.H), isSigned = false)
case _ => throw new IllegalArgumentException
}
case v: VariableInMemory =>
import ZRegister._
v.typ.size match {
@ -1139,7 +1146,8 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
def calculateStackAddressToHL(ctx: CompilationContext, baseOffset: Int): List[ZLine] = {
if (ctx.options.flag(CompilationFlag.UseIxForStack) || ctx.options.flag(CompilationFlag.UseIyForStack)) {
???
// TODO: is this correct?
List(ZLine.ldImm16(ZRegister.HL, baseOffset + ctx.extraStackOffset), ZLine.registers(ZOpcode.ADD_16, ZRegister.HL, ZRegister.SP))
} else if (ctx.options.flag(CompilationFlag.EmitSharpOpcodes)) {
List(ZLine.imm8(ZOpcode.LD_HLSP, baseOffset + ctx.extraStackOffset))
} else {
@ -1592,7 +1600,22 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case None =>
rhs match {
case VariableExpression(vname) =>
env.get[Variable](vname) match {
env.get[VariableLikeThing](vname) match {
case s: StackOffsetThing =>
s.subbyte match {
case None =>
val list = List(
calculateStackAddressToHL(ctx, s.offset) :+ ZLine.ld8(ZRegister.A, ZRegister.L),
calculateStackAddressToHL(ctx, s.offset) :+ ZLine.ld8(ZRegister.A, ZRegister.H)) ++ List.fill(size)(List(ZLine.ldImm8(ZRegister.A, 0)))
list.take(size)
case Some(0) =>
val list = (calculateStackAddressToHL(ctx, s.offset) :+ ZLine.ld8(ZRegister.A, ZRegister.L)) :: List.fill(size)(List(ZLine.ldImm8(ZRegister.A, 0)))
list.take(size)
case Some(1) =>
val list = (calculateStackAddressToHL(ctx, s.offset) :+ ZLine.ld8(ZRegister.A, ZRegister.H)) :: List.fill(size)(List(ZLine.ldImm8(ZRegister.A, 0)))
list.take(size)
case _ => throw new IllegalArgumentException
}
case v: VariableInMemory =>
List.tabulate(size) { i =>
if (i < v.typ.size) {

View File

@ -1471,6 +1471,12 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
if (stmt.stack) {
val v = StackVariable(prefix + name, typ, this.baseStackOffset)
addVariable(options, name, v, stmt.position)
addThing(StackOffsetThing(v.name + ".addr", this.baseStackOffset, get[Type]("pointer"), None), stmt.position)
addThing(StackOffsetThing(v.name + ".addr.lo", this.baseStackOffset, b, Some(0)), stmt.position)
addThing(StackOffsetThing(v.name + ".addr.hi", this.baseStackOffset, b, Some(1)), stmt.position)
addThing(StackOffsetThing(v.name + ".pointer", this.baseStackOffset, PointerType("pointer."+v.typ.name, v.typ.name, Some(v.typ)), None), stmt.position)
addThing(StackOffsetThing(v.name + ".pointer.lo", this.baseStackOffset, b, Some(0)), stmt.position)
addThing(StackOffsetThing(v.name + ".pointer.hi", this.baseStackOffset, b, Some(1)), stmt.position)
baseStackOffset += typ.size
} else {
val (v, addr) = stmt.address.fold[(VariableInMemory, Constant)]({
@ -1736,7 +1742,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
if (options.flag(CompilationFlag.SoftwareStack)) {
if (!things.contains("__sp")) {
things("__sp") = UninitializedMemoryVariable("__sp", b, VariableAllocationMethod.Auto, None, NoAlignment, isVolatile = false)
things("__stack") = UninitializedArray("__stack", 256, None, b, b, readOnly = false, WithinPageAlignment)
things("__stack") = UninitializedArray("__stack", 256, None, b, b, readOnly = false, DivisibleAlignment(256))
}
}
}

View File

@ -383,6 +383,10 @@ case class ConstantThing(name: String, value: Constant, typ: Type) extends Typed
def map(f: Constant => Constant) = ConstantThing("", f(value), typ)
}
case class StackOffsetThing(name: String, offset: Int, typ: Type, subbyte: Option[Int]) extends TypedThing with VariableLikeThing {
}
trait ParamSignature {
def types: List[Type]

View File

@ -290,4 +290,105 @@ class StackVarSuite extends FunSuite with Matchers {
m.readLong(0xc000) should equal(400)
}
}
}
test("Pointers to stack variables 1") {
val code =
"""
| noinline void inc(pointer.byte p) {
| p[0] += 1
| test1 = word(p)
| }
| byte output @$c000
| word test1 @$c002
| word stkptr @$c004
| void main () {
| stack byte a
| a = 0
| inc(a.pointer) //
| inc(a.pointer) //
| inc(a.pointer) //
| inc(a.pointer) //
| inc(a.pointer) //
| inc(a.pointer) //
| inc(a.pointer) //
| inc(a.pointer) //
| inc(a.pointer) //
| output = a
| #if ARCH_6502
| asm {
| tsx
| stx stkptr
| ldx #0
| stx stkptr+1
| }
| #endif
| #if ARCH_I80 && CPUFEATURE_8080
| #pragma zilog_syntax
| asm {
| ld hl,0
| add hl,sp
| ld (stkptr),hl
| }
| #endif
| #if CPUFEATURE_GAMEBOY
| #pragma zilog_syntax
| asm {
| ld hl,0
| add hl,sp
| ld a,l
| ld (stkptr),a
| ld a,h
| ld (stkptr+1),a
| }
| #endif
| }
""".stripMargin
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(code) { m =>
println("$" + m.readWord(0xc002).toHexString)
// (0x1f0 until 0x200).foreach { a =>
// printf(f"$$$a%04x = $$${m.readByte(a)}%02x%n");
// }
// (0xc000 until 0xc010).foreach { a =>
// printf(f"$$$a%04x = $$${m.readByte(a)}%02x%n");
// }
println("$" + m.readWord(0xc004).toHexString)
m.readByte(0xc000) should equal(code.count(_ == '↑'))
}
EmuSoftwareStackBenchmarkRun(code) { m =>
println("$" + m.readWord(0xc002).toHexString)
println("$" + m.readWord(0xc004).toHexString)
m.readByte(0xc000) should equal(code.count(_ == '↑'))
}
}
test("Pointers to stack variables 2") {
val code =
"""
| import zp_reg
| void main () {
| stack byte a
| a = 0
| byte b
| word w
| b += a.addr.lo
| w += a.addr
| w <<= a.addr.hi
| b *= a.addr.lo
| b <<= a.addr.lo
| w = nonet(b << a.addr.lo)
| w &= a.addr
| w = w | a.addr
|
| }
""".stripMargin
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)(code) { m =>
}
EmuSoftwareStackBenchmarkRun(code) { m =>
}
}
}