mirror of
https://github.com/KarolS/millfork.git
synced 2024-12-29 17:31:18 +00:00
Inline assembly improvements
This commit is contained in:
parent
9410b8f9e3
commit
c599db0068
@ -58,11 +58,12 @@ Assembly functions can be declared as `inline` or not.
|
||||
|
||||
An inline assembly function is inserted into the calling function like an inline assembly block,
|
||||
and therefore usually it shouldn't end with `RTS` or `RTI`.
|
||||
The return type on inline functions has to be `void`.
|
||||
|
||||
A non-inline assembly function should end with `RTS`, `JMP` or `RTI` as appropriate,
|
||||
or it should be an external function.
|
||||
Their return type can be any valid return type, like for Millfork functions.
|
||||
|
||||
For both inline and non-inline assembly functions,
|
||||
the return type can be any valid return type, like for Millfork functions.
|
||||
If the size of the return type is one byte,
|
||||
then the result is passed via the accumulator.
|
||||
If the size of the return type is two bytes,
|
||||
@ -103,6 +104,8 @@ Non-inline functions can only have their parameters passed via registers:
|
||||
|
||||
* `word xa`, `word ax`, `word ay`, `word ya`, `word xy`, `word yx`: a 2-byte word byte passed via given two CPU registers, with the high byte passed through the first register and the low byte passed through the second register
|
||||
|
||||
Inline assembly functions can have maximum one parameter passed via a register.
|
||||
|
||||
### External functions
|
||||
|
||||
An external function should be declared with a defined memory address
|
||||
|
@ -4,13 +4,12 @@ word nmi_routine_addr @$FFFA
|
||||
word reset_routine_addr @$FFFC
|
||||
word irq_routine_addr @$FFFE
|
||||
|
||||
inline asm void poke(word const addr, byte const value) {
|
||||
?LDA #value
|
||||
inline asm void poke(word const addr, byte a) {
|
||||
STA addr
|
||||
}
|
||||
|
||||
inline asm byte peek(word const addr) {
|
||||
LDA addr
|
||||
?LDA addr
|
||||
}
|
||||
|
||||
inline asm void disable_irq() {
|
||||
|
@ -1105,14 +1105,14 @@ object MfCompiler {
|
||||
}
|
||||
lookupFunction(ctx, f) match {
|
||||
case function: InlinedFunction =>
|
||||
inlineFunction(function, params, Some(ctx)).map {
|
||||
val (paramPreparation, statements) = inlineFunction(ctx, function, params)
|
||||
paramPreparation ++ statements.map {
|
||||
case AssemblyStatement(opcode, addrMode, expression, elidable) =>
|
||||
val param = env.evalForAsm(expression).getOrElse {
|
||||
ErrorReporting.error("Inlining failed due to non-constant things", expression.position)
|
||||
Constant.Zero
|
||||
}
|
||||
AssemblyLine(opcode, addrMode, param, elidable)
|
||||
|
||||
}
|
||||
case function: EmptyFunction =>
|
||||
??? // TODO: type conversion?
|
||||
@ -1344,18 +1344,20 @@ object MfCompiler {
|
||||
SequenceChunk(statements.map(s => compile(ctx, s)))
|
||||
}
|
||||
|
||||
def inlineFunction(i: InlinedFunction, params: List[Expression], cc: Option[CompilationContext]): List[ExecutableStatement] = {
|
||||
def inlineFunction(ctx: CompilationContext, i: InlinedFunction, params: List[Expression]): (List[AssemblyLine], List[ExecutableStatement]) = {
|
||||
var paramPreparation = List[AssemblyLine]()
|
||||
var actualCode = i.code
|
||||
i.params match {
|
||||
case AssemblyParamSignature(assParams) =>
|
||||
var hadRegisterParam = false
|
||||
assParams.zip(params).foreach {
|
||||
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
|
||||
actualParam match {
|
||||
case VariableExpression(vname) =>
|
||||
cc.foreach(_.env.get[ThingInMemory](vname))
|
||||
ctx.env.get[ThingInMemory](vname)
|
||||
case l: LhsExpression =>
|
||||
// TODO: ??
|
||||
cc.foreach(c => compileByteStorage(c, Register.A, l))
|
||||
compileByteStorage(ctx, Register.A, l)
|
||||
case _ =>
|
||||
ErrorReporting.error("A non-assignable expression was passed to an inlineable function as a `ref` parameter", actualParam.position)
|
||||
}
|
||||
@ -1365,12 +1367,18 @@ object MfCompiler {
|
||||
case x => x
|
||||
}
|
||||
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByConstant), actualParam) =>
|
||||
cc.foreach(_.env.eval(actualParam).getOrElse(Constant.error("Non-constant expression was passed to an inlineable function as a `const` parameter", actualParam.position)))
|
||||
ctx.env.eval(actualParam).getOrElse(Constant.error("Non-constant expression was passed to an inlineable function as a `const` parameter", actualParam.position))
|
||||
actualCode = actualCode.map {
|
||||
case a@AssemblyStatement(_, _, expr, _) =>
|
||||
a.copy(expression = expr.replaceVariable(ph, actualParam))
|
||||
case x => x
|
||||
}
|
||||
case (AssemblyParam(typ, v@RegisterVariable(register, _), AssemblyParameterPassingBehaviour.Copy), actualParam) =>
|
||||
if (hadRegisterParam) {
|
||||
ErrorReporting.error("Only one inline assembly function parameter can be passed via a register")
|
||||
}
|
||||
hadRegisterParam = true
|
||||
paramPreparation = compile(ctx, actualParam, Some(typ, v), BranchSpec.None)
|
||||
case (AssemblyParam(_, _, AssemblyParameterPassingBehaviour.Copy), actualParam) =>
|
||||
???
|
||||
case (_, actualParam) =>
|
||||
@ -1378,7 +1386,18 @@ object MfCompiler {
|
||||
case NormalParamSignature(Nil) =>
|
||||
case NormalParamSignature(normalParams) => ???
|
||||
}
|
||||
actualCode
|
||||
// fix local labels:
|
||||
// TODO: do it even if the labels are in an inline assembly block inside a Millfork function
|
||||
val localLabels = actualCode.flatMap{
|
||||
case AssemblyStatement(LABEL, _, VariableExpression(l), _) => Some(l)
|
||||
case _ => None
|
||||
}.toSet
|
||||
val labelPrefix = nextLabel("il")
|
||||
paramPreparation -> actualCode.map{
|
||||
case s@AssemblyStatement(_, _, VariableExpression(v), _) if localLabels(v) =>
|
||||
s.copy(expression = VariableExpression(labelPrefix + v))
|
||||
case s => s
|
||||
}
|
||||
}
|
||||
|
||||
def stackPointerFixAtBeginning(ctx: CompilationContext): List[AssemblyLine] = {
|
||||
@ -1483,7 +1502,8 @@ object MfCompiler {
|
||||
case ExpressionStatement(e@FunctionCallExpression(name, params)) =>
|
||||
env.lookupFunction(name, params.map(p => getExpressionType(ctx, p) -> p)) match {
|
||||
case Some(i: InlinedFunction) =>
|
||||
compile(ctx, inlineFunction(i, params, Some(ctx)))
|
||||
val (paramPreparation, inlinedStatements) = inlineFunction(ctx, i, params)
|
||||
SequenceChunk(List(LinearChunk(paramPreparation), compile(ctx, inlinedStatements)))
|
||||
case _ =>
|
||||
LinearChunk(compile(ctx, e, None, NoBranching))
|
||||
}
|
||||
|
@ -13,8 +13,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
||||
test("Poke test 1") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| inline asm void poke(word ref addr, byte const value) {
|
||||
| ?LDA #value
|
||||
| inline asm void poke(word ref addr, byte a) {
|
||||
| STA addr
|
||||
| }
|
||||
|
|
||||
@ -46,8 +45,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
||||
test("Poke test 2") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| inline asm void poke(word const addr, byte const value) {
|
||||
| ?LDA #value
|
||||
| inline asm void poke(word const addr, byte a) {
|
||||
| STA addr
|
||||
| }
|
||||
|
|
||||
@ -60,6 +58,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
||||
m.readByte(0xc000) should equal(5)
|
||||
}
|
||||
}
|
||||
|
||||
test("Peek test 2") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
@ -77,4 +76,27 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
||||
m.readByte(0xc000) should equal(5)
|
||||
}
|
||||
}
|
||||
|
||||
test("Labels test") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| inline asm void doNothing () {
|
||||
| JMP label
|
||||
| label:
|
||||
| }
|
||||
|
|
||||
| byte output @$c000
|
||||
| void main () {
|
||||
| output = 0
|
||||
| doNothing()
|
||||
| output += 1
|
||||
| doNothing()
|
||||
| output += 1
|
||||
| doNothing()
|
||||
| output += 1
|
||||
| }
|
||||
""".stripMargin) { m =>
|
||||
m.readByte(0xc000) should equal(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user