mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-27 11:30:19 +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,
|
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`.
|
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,
|
A non-inline assembly function should end with `RTS`, `JMP` or `RTI` as appropriate,
|
||||||
or it should be an external function.
|
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,
|
If the size of the return type is one byte,
|
||||||
then the result is passed via the accumulator.
|
then the result is passed via the accumulator.
|
||||||
If the size of the return type is two bytes,
|
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
|
* `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
|
### External functions
|
||||||
|
|
||||||
An external function should be declared with a defined memory address
|
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 reset_routine_addr @$FFFC
|
||||||
word irq_routine_addr @$FFFE
|
word irq_routine_addr @$FFFE
|
||||||
|
|
||||||
inline asm void poke(word const addr, byte const value) {
|
inline asm void poke(word const addr, byte a) {
|
||||||
?LDA #value
|
|
||||||
STA addr
|
STA addr
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asm byte peek(word const addr) {
|
inline asm byte peek(word const addr) {
|
||||||
LDA addr
|
?LDA addr
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asm void disable_irq() {
|
inline asm void disable_irq() {
|
||||||
|
@ -1105,14 +1105,14 @@ object MfCompiler {
|
|||||||
}
|
}
|
||||||
lookupFunction(ctx, f) match {
|
lookupFunction(ctx, f) match {
|
||||||
case function: InlinedFunction =>
|
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) =>
|
case AssemblyStatement(opcode, addrMode, expression, elidable) =>
|
||||||
val param = env.evalForAsm(expression).getOrElse {
|
val param = env.evalForAsm(expression).getOrElse {
|
||||||
ErrorReporting.error("Inlining failed due to non-constant things", expression.position)
|
ErrorReporting.error("Inlining failed due to non-constant things", expression.position)
|
||||||
Constant.Zero
|
Constant.Zero
|
||||||
}
|
}
|
||||||
AssemblyLine(opcode, addrMode, param, elidable)
|
AssemblyLine(opcode, addrMode, param, elidable)
|
||||||
|
|
||||||
}
|
}
|
||||||
case function: EmptyFunction =>
|
case function: EmptyFunction =>
|
||||||
??? // TODO: type conversion?
|
??? // TODO: type conversion?
|
||||||
@ -1344,18 +1344,20 @@ object MfCompiler {
|
|||||||
SequenceChunk(statements.map(s => compile(ctx, s)))
|
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
|
var actualCode = i.code
|
||||||
i.params match {
|
i.params match {
|
||||||
case AssemblyParamSignature(assParams) =>
|
case AssemblyParamSignature(assParams) =>
|
||||||
|
var hadRegisterParam = false
|
||||||
assParams.zip(params).foreach {
|
assParams.zip(params).foreach {
|
||||||
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
|
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByReference), actualParam) =>
|
||||||
actualParam match {
|
actualParam match {
|
||||||
case VariableExpression(vname) =>
|
case VariableExpression(vname) =>
|
||||||
cc.foreach(_.env.get[ThingInMemory](vname))
|
ctx.env.get[ThingInMemory](vname)
|
||||||
case l: LhsExpression =>
|
case l: LhsExpression =>
|
||||||
// TODO: ??
|
// TODO: ??
|
||||||
cc.foreach(c => compileByteStorage(c, Register.A, l))
|
compileByteStorage(ctx, Register.A, l)
|
||||||
case _ =>
|
case _ =>
|
||||||
ErrorReporting.error("A non-assignable expression was passed to an inlineable function as a `ref` parameter", actualParam.position)
|
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 x => x
|
||||||
}
|
}
|
||||||
case (AssemblyParam(typ, Placeholder(ph, phType), AssemblyParameterPassingBehaviour.ByConstant), actualParam) =>
|
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 {
|
actualCode = actualCode.map {
|
||||||
case a@AssemblyStatement(_, _, expr, _) =>
|
case a@AssemblyStatement(_, _, expr, _) =>
|
||||||
a.copy(expression = expr.replaceVariable(ph, actualParam))
|
a.copy(expression = expr.replaceVariable(ph, actualParam))
|
||||||
case x => x
|
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 (AssemblyParam(_, _, AssemblyParameterPassingBehaviour.Copy), actualParam) =>
|
||||||
???
|
???
|
||||||
case (_, actualParam) =>
|
case (_, actualParam) =>
|
||||||
@ -1378,7 +1386,18 @@ object MfCompiler {
|
|||||||
case NormalParamSignature(Nil) =>
|
case NormalParamSignature(Nil) =>
|
||||||
case NormalParamSignature(normalParams) => ???
|
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] = {
|
def stackPointerFixAtBeginning(ctx: CompilationContext): List[AssemblyLine] = {
|
||||||
@ -1483,7 +1502,8 @@ object MfCompiler {
|
|||||||
case ExpressionStatement(e@FunctionCallExpression(name, params)) =>
|
case ExpressionStatement(e@FunctionCallExpression(name, params)) =>
|
||||||
env.lookupFunction(name, params.map(p => getExpressionType(ctx, p) -> p)) match {
|
env.lookupFunction(name, params.map(p => getExpressionType(ctx, p) -> p)) match {
|
||||||
case Some(i: InlinedFunction) =>
|
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 _ =>
|
case _ =>
|
||||||
LinearChunk(compile(ctx, e, None, NoBranching))
|
LinearChunk(compile(ctx, e, None, NoBranching))
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
|||||||
test("Poke test 1") {
|
test("Poke test 1") {
|
||||||
EmuBenchmarkRun(
|
EmuBenchmarkRun(
|
||||||
"""
|
"""
|
||||||
| inline asm void poke(word ref addr, byte const value) {
|
| inline asm void poke(word ref addr, byte a) {
|
||||||
| ?LDA #value
|
|
||||||
| STA addr
|
| STA addr
|
||||||
| }
|
| }
|
||||||
|
|
|
|
||||||
@ -46,8 +45,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
|||||||
test("Poke test 2") {
|
test("Poke test 2") {
|
||||||
EmuBenchmarkRun(
|
EmuBenchmarkRun(
|
||||||
"""
|
"""
|
||||||
| inline asm void poke(word const addr, byte const value) {
|
| inline asm void poke(word const addr, byte a) {
|
||||||
| ?LDA #value
|
|
||||||
| STA addr
|
| STA addr
|
||||||
| }
|
| }
|
||||||
|
|
|
|
||||||
@ -60,6 +58,7 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
|||||||
m.readByte(0xc000) should equal(5)
|
m.readByte(0xc000) should equal(5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("Peek test 2") {
|
test("Peek test 2") {
|
||||||
EmuBenchmarkRun(
|
EmuBenchmarkRun(
|
||||||
"""
|
"""
|
||||||
@ -77,4 +76,27 @@ class InlineAssemblyFunctionsSuite extends FunSuite with Matchers {
|
|||||||
m.readByte(0xc000) should equal(5)
|
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…
x
Reference in New Issue
Block a user