1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-02-22 12:29:00 +00:00

Allow refering to labels from other functions in assembly (fixes #101)

This commit is contained in:
Karol Stasiak 2021-02-24 02:32:00 +01:00
parent ff6106a838
commit 196ad6542f
7 changed files with 123 additions and 22 deletions

View File

@ -52,6 +52,17 @@ Global label names have to start with a letter and can contain digits, underscor
Local label names (available since Millfork 0.3.22) start with a period and are visible only in the given function. Local label names (available since Millfork 0.3.22) start with a period and are visible only in the given function.
Anonymous labels designated with `+` or `-` are also not supported. Anonymous labels designated with `+` or `-` are also not supported.
Referring to a global label with an offset requires wrapping it in `label(…)`:
STA .local_opcode // ok
STA label(.local_opcode) // ok
STA .local_opcode + 1 // ok
STA label(.local_opcode) + 1 // ok
STA global_opcode // ok
STA label(global_opcode) // ok
STA global_opcode + 1 // NOT OK
sta label(global_opcode) + 1 // ok
Assembly can refer to variables and constants defined in Millfork, Assembly can refer to variables and constants defined in Millfork,
but you need to be careful with using absolute vs immediate addressing: but you need to be careful with using absolute vs immediate addressing:

View File

@ -27,6 +27,17 @@ Global label names have to start with a letter and can contain digits, underscor
Local label names (available since Millfork 0.3.22) start with a period and are visible only in the given function. Local label names (available since Millfork 0.3.22) start with a period and are visible only in the given function.
Anonymous labels designated with `+` or `-` are also not supported. Anonymous labels designated with `+` or `-` are also not supported.
Referring to a global label with an offset requires wrapping it in `label(…)`:
STA .local_opcode // ok
STA label(.local_opcode) // ok
STA .local_opcode + 1 // ok
STA label(.local_opcode) + 1 // ok
STA global_opcode // ok
STA label(global_opcode) // ok
STA global_opcode + 1 // NOT OK
STA label(global_opcode) + 1 // ok
Assembly can refer to variables and constants defined in Millfork, Assembly can refer to variables and constants defined in Millfork,
but you need to be careful with using absolute vs immediate addressing: but you need to be careful with using absolute vs immediate addressing:

View File

@ -52,6 +52,17 @@ Global label names have to start with a letter and can contain digits, underscor
Local label names (available since Millfork 0.3.22) start with a period and are visible only in the given function. Local label names (available since Millfork 0.3.22) start with a period and are visible only in the given function.
Anonymous labels designated with `+` or `-` are also not supported. Anonymous labels designated with `+` or `-` are also not supported.
Referring to a global label with an offset requires wrapping it in `label(…)`:
LD (.local_opcode),A // ok
LD (label(.local_opcode)),A // ok
LD (.local_opcode + 1),A // ok
LD (label(.local_opcode) + 1),A // ok
LD (global_opcode),A // ok
LD (label(global_opcode)),A // ok
LD (global_opcode + 1),A // NOT OK
LD (label(global_opcode) + 1),A // ok
Assembly can refer to variables and constants defined in Millfork, Assembly can refer to variables and constants defined in Millfork,
but you need to be careful with using absolute vs immediate addressing: but you need to be careful with using absolute vs immediate addressing:

View File

@ -6,7 +6,7 @@ import millfork.assembly.m6809.{Inherent, MLine, MOpcode, NonExistent}
import millfork.compiler.{AbstractCompiler, AbstractExpressionCompiler, AbstractStatementCompiler, BranchSpec, CompilationContext} import millfork.compiler.{AbstractCompiler, AbstractExpressionCompiler, AbstractStatementCompiler, BranchSpec, CompilationContext}
import millfork.node.{Assignment, BlackHoleExpression, BreakStatement, ContinueStatement, DoWhileStatement, EmptyStatement, ExecutableStatement, Expression, ExpressionStatement, ForEachStatement, ForStatement, FunctionCallExpression, GotoStatement, IfStatement, LabelStatement, LiteralExpression, M6809AssemblyStatement, MemsetStatement, ReturnDispatchStatement, ReturnStatement, VariableExpression, WhileStatement} import millfork.node.{Assignment, BlackHoleExpression, BreakStatement, ContinueStatement, DoWhileStatement, EmptyStatement, ExecutableStatement, Expression, ExpressionStatement, ForEachStatement, ForStatement, FunctionCallExpression, GotoStatement, IfStatement, LabelStatement, LiteralExpression, M6809AssemblyStatement, MemsetStatement, ReturnDispatchStatement, ReturnStatement, VariableExpression, WhileStatement}
import millfork.assembly.m6809.MOpcode._ import millfork.assembly.m6809.MOpcode._
import millfork.env.{BooleanType, ConstantBooleanType, FatBooleanType, Label, MemoryAddressConstant, StructureConstant, ThingInMemory} import millfork.env.{BooleanType, Constant, ConstantBooleanType, FatBooleanType, Label, MemoryAddressConstant, StructureConstant, ThingInMemory}
/** /**
* @author Karol Stasiak * @author Karol Stasiak
@ -58,12 +58,21 @@ object M6809StatementCompiler extends AbstractStatementCompiler[MLine] {
} }
(eval ++ epilogue ++ rts) -> Nil (eval ++ epilogue ++ rts) -> Nil
case M6809AssemblyStatement(opcode, addrMode, expression, elidability) => case M6809AssemblyStatement(opcode, addrMode, expression, elidability) =>
ctx.env.evalForAsm(expression, opcode) match { val e = ctx.env.evalForAsm(expression) match {
case Some(e) => List(MLine(opcode, addrMode, e, elidability)) -> Nil case Some(e) => e
case None => case None =>
println(statement) expression match {
??? case VariableExpression(name) =>
env.maybeGet[ThingInMemory](name).map(_.toAddress).getOrElse {
val fqName = if (name.startsWith(".")) env.prefix + name else name
MemoryAddressConstant(Label(fqName))
} }
case _ =>
ctx.log.error("Invalid parameter", statement.position)
Constant.Zero
}
}
List(MLine(opcode, addrMode, e, elidability)) -> Nil
case Assignment(destination, source) => case Assignment(destination, source) =>
if (destination == BlackHoleExpression) return M6809ExpressionCompiler.compile(ctx, source, MExpressionTarget.NOTHING, BranchSpec.None) -> Nil if (destination == BlackHoleExpression) return M6809ExpressionCompiler.compile(ctx, source, MExpressionTarget.NOTHING, BranchSpec.None) -> Nil
val destinationType = AbstractExpressionCompiler.getExpressionType(ctx, destination) val destinationType = AbstractExpressionCompiler.getExpressionType(ctx, destination)
@ -107,13 +116,22 @@ object M6809StatementCompiler extends AbstractStatementCompiler[MLine] {
case s:ContinueStatement => case s:ContinueStatement =>
compileContinueStatement(ctx, s) -> Nil compileContinueStatement(ctx, s) -> Nil
case M6809AssemblyStatement(opcode, addrMode, expression, elidability) => case M6809AssemblyStatement(opcode, addrMode, expression, elidability) =>
ctx.env.evalForAsm(expression, opcode) match { val silent = MOpcode.Branching(opcode) || opcode == LABEL || opcode == CHANGED_MEM
case Some(param) => val param: Constant = ctx.env.evalForAsm(expression, silent = silent) match {
List(MLine(opcode, addrMode, param, elidability)) -> Nil case Some(param) => param
case None => case None =>
ctx.log.error("Invalid parameter", expression.position) expression match {
Nil -> Nil case VariableExpression(name) =>
env.maybeGet[ThingInMemory](name).map(_.toAddress).getOrElse{
val fqName = if (name.startsWith(".")) env.prefix + name else name
MemoryAddressConstant(Label(fqName))
} }
case _ =>
ctx.log.error("Invalid parameter", expression.position)
Constant.Zero
}
}
List(MLine(opcode, addrMode, param, elidability)) -> Nil
case s:LabelStatement => case s:LabelStatement =>
List(MLine.label(env.prefix + s.name)) -> Nil List(MLine.label(env.prefix + s.name)) -> Nil
case s: GotoStatement => case s: GotoStatement =>

View File

@ -342,11 +342,10 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] {
x match { x match {
// TODO: hmmm // TODO: hmmm
case VariableExpression(name) => case VariableExpression(name) =>
if (OpcodeClasses.ShortBranching(o) || o == JMP || o == LABEL || o == CHANGED_MEM || OpcodeClasses.HudsonTransfer(o)) { val silent = OpcodeClasses.ShortBranching(o) || o == JMP || o == LABEL || o == CHANGED_MEM || OpcodeClasses.HudsonTransfer(o)
env.evalForAsm(x, silent = silent).orElse(env.maybeGet[ThingInMemory](name).map(_.toAddress)).getOrElse{
val fqName = if (name.startsWith(".")) env.prefix + name else name val fqName = if (name.startsWith(".")) env.prefix + name else name
MemoryAddressConstant(Label(fqName)) MemoryAddressConstant(Label(fqName))
} else {
env.evalForAsm(x).getOrElse(env.get[ThingInMemory](name, x.position).toAddress)
} }
case FunctionCallExpression("byte_and_pointer$", List(z, b@VariableExpression(name))) => case FunctionCallExpression("byte_and_pointer$", List(z, b@VariableExpression(name))) =>
StructureConstant(env.get[StructType]("byte_and_pointer$"), List( StructureConstant(env.get[StructType]("byte_and_pointer$"), List(

View File

@ -973,15 +973,15 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
} }
} }
def evalForAsm(e: Expression, op: MOpcode.Value = MOpcode.NOP, silent: Boolean = false): Option[Constant] = { def evalForAsm(e: Expression, silent: Boolean = false): Option[Constant] = {
e match { e match {
// TODO: hmmm case FunctionCallExpression("label", List(VariableExpression(name))) if (!name.contains(".")) =>
case VariableExpression(name) => return Some(Label(name).toAddress)
import MOpcode._ case FunctionCallExpression("label", List(VariableExpression(name))) if (name.startsWith(".")) =>
if (MOpcode.Branching(op) || op == LABEL || op == CHANGED_MEM) { return Some(Label(prefix + name).toAddress)
val fqName = if (name.startsWith(".")) prefix + name else name case FunctionCallExpression("label", _) =>
return Some(MemoryAddressConstant(Label(fqName))) log.error("Invalid label reference", e.position)
} return Some(Constant.Zero)
case _ => case _ =>
} }
e match { e match {
@ -994,7 +994,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case None => case None =>
if (name.startsWith(".")) Some(Label(prefix + name).toAddress) if (name.startsWith(".")) Some(Label(prefix + name).toAddress)
else { else {
if (!silent) log.warn(s"$name is not known", e.position) if (!silent) log.warn(s"$name is not known. If it is a label, consider wrapping it in label(...).", e.position)
None None
} }
} }

View File

@ -28,6 +28,7 @@ class AssemblySuite extends FunSuite with Matchers with AppendedClues {
| // w&x | // w&x
| asm void main () { | asm void main () {
| lda #3 | lda #3
| sta label(.l)+1
| sta .l+1 | sta .l+1
| .l: lda #55 | .l: lda #55
| sta output | sta output
@ -36,6 +37,56 @@ class AssemblySuite extends FunSuite with Matchers with AppendedClues {
""".stripMargin)(_.readByte(0xc000) should equal(3)) """.stripMargin)(_.readByte(0xc000) should equal(3))
} }
test("Self-modifying assembly 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Motorola6809)(
"""
| byte output @$c000
| // w&x
| asm void f() {
| f_data:
| lda #$ff
| rts
| }
|
| asm void main () {
| lda f_data
| sta f_data
| lda #3
| sta label(f_data)+1
| lda #0
| jsr f_data
| sta output
| lda #lo(f)
| rts
| ignored:}
""".stripMargin)(_.readByte(0xc000) should equal(3))
}
test("Self-modifying assembly 2 (Z80)") {
EmuCrossPlatformBenchmarkRun(Cpu.Z80)(
"""
| byte output @$c000
| // w&x
| asm void f() {
| f_data:
| ld a, $ff
| ret
| }
|
| asm void main () {
| ld a,(f_data)
| ld (f_data),a
| ld a,3
| ld (label(f_data)+1),a
| ld a,0
| call f_data
| ld (output),a
| ld b,lo(f)
| ret
| ignored:}
""".stripMargin)(_.readByte(0xc000) should equal(3))
}
test("Assembly functions") { test("Assembly functions") {
EmuBenchmarkRun( EmuBenchmarkRun(
""" """