1
0
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:
Karol Stasiak 2018-02-01 00:22:53 +01:00
parent 9410b8f9e3
commit c599db0068
4 changed files with 61 additions and 17 deletions

View File

@ -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

View File

@ -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() {

View File

@ -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))
}

View File

@ -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)
}
}
}