mirror of
https://github.com/irmen/prog8.git
synced 2024-11-22 15:33:02 +00:00
added call
builtin function for indirect JSR
This commit is contained in:
parent
43944a94eb
commit
ae66fcac1e
@ -133,6 +133,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"rrestore" to FSignature(false, emptyList(), null),
|
||||
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"call" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), null),
|
||||
)
|
||||
|
||||
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
|
||||
|
@ -85,6 +85,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
"rrestore" -> funcRrestore()
|
||||
"cmp" -> funcCmp(fcall)
|
||||
"callfar" -> funcCallFar(fcall, resultRegister)
|
||||
"call" -> funcCall(fcall)
|
||||
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
|
||||
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
|
||||
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
|
||||
@ -194,6 +195,19 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
plp""")
|
||||
}
|
||||
|
||||
private fun funcCall(fcall: PtBuiltinFunctionCall) {
|
||||
val constAddr = fcall.args[0].asConstInteger()
|
||||
if(constAddr!=null) {
|
||||
asmgen.out(" jsr ${constAddr.toHex()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY) // jump address
|
||||
asmgen.out("""
|
||||
sta (+)+1
|
||||
sty (+)+2
|
||||
+ jsr 0 ; modified""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
|
@ -436,7 +436,6 @@ $loopLabel sty $indexVar
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT -> {
|
||||
numElements!!
|
||||
val indexVar = asmgen.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
|
@ -26,7 +26,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||
|
||||
val symbol = asmgen.symbolTable.lookup(call.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
val sub = symbol?.astNode as IPtSubroutine
|
||||
val subAsmName = asmgen.asmSymbolName(call.name)
|
||||
|
||||
if(sub is PtAsmSub) {
|
||||
|
@ -26,6 +26,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"pushw" -> funcPushw(call)
|
||||
"rsave", "rrestore" -> ExpressionCodeResult.EMPTY // vm doesn't have registers to save/restore
|
||||
"callfar" -> funcCallfar(call)
|
||||
"call" -> funcCall(call)
|
||||
"msb" -> funcMsb(call)
|
||||
"lsb" -> funcLsb(call)
|
||||
"memory" -> funcMemory(call)
|
||||
@ -71,6 +72,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCall(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val addressTr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, addressTr, addressTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 3), null)
|
||||
|
@ -1254,6 +1254,15 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
private fun checkFunctionCall(target: Statement, args: List<Expression>, position: Position) {
|
||||
if(target is BuiltinFunctionPlaceholder) {
|
||||
if(target.name=="call") {
|
||||
if(args[0] is AddressOf)
|
||||
errors.err("can't call this indirectly, just use normal function call syntax", args[0].position)
|
||||
if(args[0] is IdentifierReference) {
|
||||
val callTarget = (args[0] as IdentifierReference).targetStatement(program)
|
||||
if(callTarget !is VarDecl)
|
||||
errors.err("can't call this indirectly, just use normal function call syntax", args[0].position)
|
||||
}
|
||||
}
|
||||
if(!compilerOptions.floats) {
|
||||
if (target.name == "peekf" || target.name == "pokef")
|
||||
errors.err("floating point used, but that is not enabled via options", position)
|
||||
|
@ -7,10 +7,7 @@ import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.BuiltinFunctions
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
|
||||
/**
|
||||
@ -191,6 +188,10 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
errors.err("cannot use arguments when calling a label", pos)
|
||||
}
|
||||
}
|
||||
is VarDecl -> {
|
||||
if(target.type!=VarDeclType.VAR || target.datatype!=DataType.UWORD)
|
||||
errors.err("wrong address variable datatype, expected uword", call.target.position)
|
||||
}
|
||||
null -> {}
|
||||
else -> errors.err("cannot call this as a subroutine or function", call.target.position)
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import io.kotest.core.spec.style.AnnotationSpec
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
class TestSubroutines: AnnotationSpec() {
|
||||
class TestSubroutines: FunSpec({
|
||||
|
||||
@Test
|
||||
fun stringParameterAcceptedInParser() {
|
||||
test("stringParameter AcceptedInParser") {
|
||||
// note: the *parser* should accept this as it is valid *syntax*,
|
||||
// however, the compiler itself may or may not complain about it later.
|
||||
val text = """
|
||||
@ -37,8 +40,7 @@ class TestSubroutines: AnnotationSpec() {
|
||||
func.statements.isEmpty() shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun arrayParameterAcceptedInParser() {
|
||||
test("arrayParameter AcceptedInParser") {
|
||||
// note: the *parser* should accept this as it is valid *syntax*,
|
||||
// however, the compiler itself may or may not complain about it later.
|
||||
val text = """
|
||||
@ -62,4 +64,42 @@ class TestSubroutines: AnnotationSpec() {
|
||||
func.parameters.single().type shouldBe DataType.ARRAY_UB
|
||||
func.statements.isEmpty() shouldBe true
|
||||
}
|
||||
|
||||
test("cannot call a subroutine via pointer") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword func = 12345
|
||||
func() ; error
|
||||
func(1,2,3) ; error
|
||||
cx16.r0 = func() ; error
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target(), false, src, errors, false) shouldBe null
|
||||
errors.errors.size shouldBe 3
|
||||
errors.errors[0] shouldContain "cannot call that"
|
||||
errors.errors[1] shouldContain "cannot call that"
|
||||
errors.errors[2] shouldContain "cannot call that"
|
||||
}
|
||||
|
||||
test("can call a subroutine pointer using call") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword func = 12345
|
||||
call(func) ; ok
|
||||
call(12345) ; ok
|
||||
cx16.r0 = call(func) ; error
|
||||
call(&start) ; error
|
||||
call(start) ; error
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target(), false, src, errors, false) shouldBe null
|
||||
errors.errors.size shouldBe 3
|
||||
errors.errors[0] shouldContain ":7:19: assignment right hand side doesn't result in a value"
|
||||
errors.errors[1] shouldContain "can't call this indirectly"
|
||||
errors.errors[2] shouldContain "can't call this indirectly"
|
||||
}
|
||||
})
|
||||
|
@ -966,13 +966,20 @@ memory (name, size, alignment)
|
||||
The return value is just a simple uword address so it cannot be used as an array in your program.
|
||||
You can only treat it as a pointer or use it in inline assembly.
|
||||
|
||||
call (address)
|
||||
Calls a subroutine given by its memory address. You cannot pass arguments and result values
|
||||
directly, although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example.
|
||||
This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable.
|
||||
But because it doesn't handle bank switching
|
||||
etcetera by itself, it is a lot faster than ``callfar``. And it works on other systems than just the Commander X16.
|
||||
|
||||
callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now
|
||||
Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine)
|
||||
Be aware that ram OR rom bank may be changed depending on the address it jumps to!
|
||||
The argumentword will be loaded into the A+Y registers before calling the routine.
|
||||
The uword value that the routine returns in the A+Y registers, will be returned.
|
||||
NOTE: this routine is very inefficient, so don't use it to call often. Set the bank yourself
|
||||
or even write a custom tailored trampoline routine if you need to.
|
||||
or even write a custom tailored trampoline routine if you need to. Or use ``call`` if you can.
|
||||
|
||||
syscall (callnr), syscall1 (callnr, arg), syscall2 (callnr, arg1, arg2), syscall3 (callnr, arg1, arg2, arg3)
|
||||
Functions for doing a system call on targets that support this. Currently no actual target
|
||||
|
@ -11,9 +11,6 @@ Assuming your writes are aligned to 32-bit boundaries, do four reads from the in
|
||||
(ex: lda DATA1 ; 4 times) and then stz the other one (stz DATA0).
|
||||
The cache is loaded by the DATA1 reads, and the contents are written out with the DATA0 write, 4 bytes at once.
|
||||
|
||||
- [on branch: call-pointers] allow calling a subroutine via a pointer variable (indirect JSR, optimized form of callfar())
|
||||
modify programs (shell, paint) that now use callfar
|
||||
|
||||
- [on branch: shortcircuit] investigate McCarthy evaluation again? this may also reduce code size perhaps for things like if a>4 or a<2 ....
|
||||
|
||||
...
|
||||
|
@ -1,13 +1,15 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
const uword vera_freq = (136.5811 / 0.3725290298461914) as uword
|
||||
const uword v_f_10 = (136.5811 / 0.3725290298461914 + 0.5) as uword
|
||||
uword ptr = &test
|
||||
call(ptr)
|
||||
call(ptr)
|
||||
call(ptr)
|
||||
}
|
||||
|
||||
txt.print_uw(v_f_10)
|
||||
txt.print_uw(vera_freq)
|
||||
sub test() {
|
||||
txt.print("test!\n")
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ CONTROL FLOW
|
||||
jump location - continue running at instruction at 'location' (label/memory address)
|
||||
jumpi reg1 - continue running at memory address in reg1 (indirect jump)
|
||||
preparecall numparams - indicator that the next instructions are the param setup and function call/syscall with <numparams> parameters
|
||||
calli reg1 - calls a subroutine (without arguments and without return valus) at memory addres in reg1 (indirect jsr)
|
||||
call label(argument register list) [: resultreg.type]
|
||||
- calls a subroutine with the given arguments and return value (optional).
|
||||
save current instruction location+1, continue execution at instruction nr of the label.
|
||||
@ -252,6 +253,7 @@ enum class Opcode {
|
||||
JUMP,
|
||||
JUMPI,
|
||||
PREPARECALL,
|
||||
CALLI,
|
||||
CALL,
|
||||
SYSCALL,
|
||||
RETURN,
|
||||
@ -397,6 +399,7 @@ val OpcodesThatBranch = setOf(
|
||||
Opcode.JUMPI,
|
||||
Opcode.RETURN,
|
||||
Opcode.RETURNR,
|
||||
Opcode.CALLI,
|
||||
Opcode.CALL,
|
||||
Opcode.SYSCALL,
|
||||
Opcode.BSTCC,
|
||||
@ -543,6 +546,7 @@ val instructionFormats = mutableMapOf(
|
||||
Opcode.JUMP to InstructionFormat.from("N,<a"),
|
||||
Opcode.JUMPI to InstructionFormat.from("N,<r1"),
|
||||
Opcode.PREPARECALL to InstructionFormat.from("N,<i"),
|
||||
Opcode.CALLI to InstructionFormat.from("N,<r1"),
|
||||
Opcode.CALL to InstructionFormat.from("N,call"),
|
||||
Opcode.SYSCALL to InstructionFormat.from("N,syscall"),
|
||||
Opcode.RETURN to InstructionFormat.from("N"),
|
||||
|
@ -1,4 +1,4 @@
|
||||
<filetype binary="false" default_extension="p8" description="Prog8 source file" name="Prog8">
|
||||
<filetype binary="false" default_extension="prog8" description="Prog8 source file" name="Prog8">
|
||||
<highlighting>
|
||||
<options>
|
||||
<option name="LINE_COMMENT" value=";" />
|
||||
@ -14,10 +14,10 @@
|
||||
<keywords keywords="&;->;@;and;as;asmsub;break;clobbers;continue;do;downto;else;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;or;repeat;return;romsub;step;sub;to;true;unroll;until;when;while;xor;~" ignore_case="false" />
|
||||
<keywords2 keywords="%address;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%launcher;%option;%output;%zeropage;%zpallowed;%zpreserved;atascii:;default:;iso:;petscii:;sc:" />
|
||||
<keywords3 keywords="@requirezp;@shared;@split;@zp;bool;byte;const;float;str;ubyte;uword;void;word" />
|
||||
<keywords4 keywords="abs;all;any;callfar;callram;callrom;clamp;cmp;divmod;len;lsb;max;memory;min;mkword;msb;peek;peekf;peekw;poke;pokef;pokew;pop;popw;push;pushw;reverse;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sort;sqrt;swap;|>" />
|
||||
<keywords4 keywords="abs;all;any;call;callfar;clamp;cmp;divmod;len;lsb;max;memory;min;mkword;msb;peek;peekf;peekw;poke;pokef;pokew;pop;popw;push;pushw;reverse;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sort;sqrt;swap;|>" />
|
||||
</highlighting>
|
||||
<extensionMap>
|
||||
<mapping ext="p8" />
|
||||
<mapping ext="prog8" />
|
||||
<mapping ext="p8" />
|
||||
</extensionMap>
|
||||
</filetype>
|
@ -27,7 +27,7 @@
|
||||
<Keywords name="Keywords1">void const
str
byte ubyte bool
word uword
float
zp shared split requirezp</Keywords>
|
||||
<Keywords name="Keywords2">%address
%asm
%ir
%asmbinary
%asminclude
%breakpoint
%encoding
%import
%launcher
%option
%output
%zeropage
%zpreserved
%zpallowed</Keywords>
|
||||
<Keywords name="Keywords3">inline sub asmsub romsub
clobbers
asm
if
when else
if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z
for in step do while repeat unroll
break continue return goto</Keywords>
|
||||
<Keywords name="Keywords4">abs all any callfar clamp cmp divmod len lsb lsl lsr memory mkword min max msb peek peekw peekf poke pokew pokef push pushw pop popw rsave rsavex rrestore rrestorex reverse rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sort sqrtw swap</Keywords>
|
||||
<Keywords name="Keywords4">abs all any call callfar clamp cmp divmod len lsb lsl lsr memory mkword min max msb peek peekw peekf poke pokew pokef push pushw pop popw rsave rsavex rrestore rrestorex reverse rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sort sqrtw swap</Keywords>
|
||||
<Keywords name="Keywords5">true false
not and or xor
as to downto |></Keywords>
|
||||
<Keywords name="Keywords6"></Keywords>
|
||||
<Keywords name="Keywords7"></Keywords>
|
||||
|
@ -15,7 +15,7 @@ syn keyword prog8BuiltInFunc any all len reverse sort
|
||||
" Miscellaneous functions
|
||||
syn keyword prog8BuiltInFunc cmp divmod lsb msb mkword min max peek peekw peekf poke pokew pokef push pushw pop popw rsave rsavex rrestore rrestorex
|
||||
syn keyword prog8BuiltInFunc rol rol2 ror ror2 sizeof setlsb setmsb
|
||||
syn keyword prog8BuiltInFunc swap memory callfar clamp
|
||||
syn keyword prog8BuiltInFunc swap memory call callfar clamp
|
||||
|
||||
|
||||
" c64/floats.p8
|
||||
|
@ -181,6 +181,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
Opcode.STOREZI -> InsSTOREZI(ins)
|
||||
Opcode.JUMP, Opcode.JUMPI -> InsJUMP(ins)
|
||||
Opcode.PREPARECALL -> nextPc()
|
||||
Opcode.CALLI -> throw IllegalArgumentException("VM cannot run code from memory")
|
||||
Opcode.CALL -> InsCALL(ins)
|
||||
Opcode.SYSCALL -> InsSYSCALL(ins)
|
||||
Opcode.RETURN -> InsRETURN()
|
||||
|
Loading…
Reference in New Issue
Block a user