defers are now only registered/called when flow of control actually reached the defer statement

a defer statement sets its corresponding bit in a bitmask that is shifted in the defer handler routine to see what defer blocks to call.
This commit is contained in:
Irmen de Jong 2024-10-21 00:22:42 +02:00
parent 0247fb0d84
commit a6159702da
9 changed files with 107 additions and 147 deletions

View File

@ -129,7 +129,6 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
"callfar2" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("argA", arrayOf(DataType.UBYTE)), FParam("argX", arrayOf(DataType.UBYTE)), FParam("argY", arrayOf(DataType.UBYTE)), FParam("argC", arrayOf(DataType.BOOL))), DataType.UWORD),
"call" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
"invoke_defer" to FSignature(false, emptyList(), null),
)
val InplaceModifyingBuiltinFunctions = setOf(

View File

@ -9,7 +9,6 @@ import kotlin.io.path.readText
const val internedStringsModuleName = "prog8_interned_strings"
const val deferLabel = "prog8_defer_statements"
/**

View File

@ -603,11 +603,11 @@ class AsmGen6502Internal (
is PtRepeatLoop -> translate(stmt)
is PtWhen -> translate(stmt)
is PtIncludeBinary -> translate(stmt)
is PtBreakpoint -> translate(stmt)
is PtBreakpoint -> translateBrk()
is PtVariable, is PtConstant, is PtMemMapped -> { /* do nothing; variables are handled elsewhere */ }
is PtBlock -> throw AssemblyError("block should have been handled elsewhere")
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtNodeGroup -> stmt.children.forEach { translate(it) }
is PtDefer -> translate(stmt)
is PtNop -> {}
else -> throw AssemblyError("missing asm translation for $stmt")
}
@ -1084,7 +1084,7 @@ $repeatLabel""")
out(" .binary \"$pathForAssembler\" $offset $length")
}
private fun translate(brk: PtBreakpoint) {
private fun translateBrk() {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
breakpointLabels.add(label)
out(label)
@ -1093,15 +1093,6 @@ $repeatLabel""")
}
}
private fun translate(defer: PtDefer) {
val sub = defer.definingSub()!!
out("${sub.name}.$deferLabel")
for(stmt in defer.children) {
translate(stmt)
}
out(" rts")
}
internal fun signExtendAYlsb(valueDt: DataType) {
// sign extend signed byte in A to full word in AY
when(valueDt) {

View File

@ -69,18 +69,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
"invoke_defer" -> funcInvokeDefer(fcall)
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
}
return BuiltinFunctions.getValue(fcall.name).returnType
}
private fun funcInvokeDefer(call: PtBuiltinFunctionCall) {
val sub = call.definingSub()!!
asmgen.out(" jsr ${sub.name}.$deferLabel")
}
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType, resultRegister: RegisterOrPair?) {
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
when (resultType) {

View File

@ -44,19 +44,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
"invoke_defer" -> funcInvokeDefer(call)
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
}
}
private fun funcInvokeDefer(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val sub = call.definingSub()!!
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = "${sub.name}.$deferLabel",
fcallArgs = FunctionCallArgs(emptyList(), emptyList())), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val valueTr = exprGen.translateExpression(call.args[0])

View File

@ -235,7 +235,6 @@ class IRCodeGen(
listOf(chunk)
}
is PtConditionalBranch -> translate(node)
is PtDefer -> translate(node)
is PtInlineAssembly -> listOf(IRInlineAsmChunk(null, node.assembly, node.isIR, null))
is PtIncludeBinary -> listOf(IRInlineBinaryChunk(null, readBinaryData(node), null))
is PtAddressOf,
@ -255,6 +254,7 @@ class IRCodeGen(
is PtBool,
is PtArray,
is PtBlock,
is PtDefer -> throw AssemblyError("defer should have been transformed")
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
is PtSub -> throw AssemblyError("nested subroutines should have been flattened ${node.position}")
else -> TODO("missing codegen for $node")
@ -273,16 +273,6 @@ class IRCodeGen(
.map { it.toUByte() }
}
private fun translate(defer: PtDefer): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
for(stmt in defer.children) {
result += translateNode(stmt)
}
addInstr(result, IRInstruction(Opcode.RETURN), null)
val sub = defer.definingSub()!!
return labelFirstChunk(result, "${sub.name}.$deferLabel")
}
private fun translate(branch: PtConditionalBranch): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()

View File

@ -6,12 +6,20 @@ import prog8.code.core.*
internal fun postprocessIntermediateAst(program: PtProgram, st: SymbolTable, errors: IErrorReporter) {
coalesceDefers(program)
integrateDefers(program, st)
processDefers(program, st, errors)
}
private fun coalesceDefers(program: PtProgram) {
private fun processDefers(program: PtProgram, st: SymbolTable, errors: IErrorReporter) {
val defers = setDeferMasks(program, errors)
if(errors.noErrors())
integrateDefers(defers, program, st)
}
private const val maskVarName = "prog8_defers_mask"
private const val invokeDefersRoutineName = "prog8_invoke_defers"
private fun setDeferMasks(program: PtProgram, errors: IErrorReporter): Map<PtSub, List<PtDefer>> {
val defersPerSub = mutableMapOf<PtSub, MutableList<PtDefer>>().withDefault { mutableListOf() }
walkAst(program) { node, _ ->
@ -24,58 +32,79 @@ private fun coalesceDefers(program: PtProgram) {
}
for((sub, defers) in defersPerSub) {
val coalescedDefer = PtDefer(sub.position)
for(defer in defers.reversed()) {
for(stmt in defer.children)
coalescedDefer.add(stmt)
sub.children.remove(defer)
if(defers.isEmpty())
continue
if (defers.size > 8) {
errors.err("can have no more than 8 defers per subroutine", sub.position)
return emptyMap()
}
if(coalescedDefer.children.isNotEmpty()) {
sub.add(coalescedDefer)
// define the bitmask variable and set it to zero
val deferVariable = PtVariable(maskVarName, DataType.UBYTE, ZeropageWish.NOT_IN_ZEROPAGE, null, null, sub.position)
val assignZero = PtAssignment(sub.position)
assignZero.add(PtAssignTarget(false, sub.position).also {
it.add(PtIdentifier(sub.scopedName+"."+maskVarName, DataType.UBYTE, sub.position))
})
assignZero.add(PtNumber(DataType.UBYTE, 0.0, sub.position))
sub.add(0, assignZero)
sub.add(0, deferVariable)
for((deferIndex, defer) in defers.withIndex()) {
// replace the defer statement with one that enables the bit in the mask for this defer
val idx = defer.parent.children.indexOf(defer)
val enableDefer = PtAugmentedAssign("|=", defer.position)
val target = PtAssignTarget(true, defer.position)
target.add(PtIdentifier(sub.scopedName+"."+maskVarName, DataType.UBYTE, defer.position))
enableDefer.add(target)
// enable the bit for this defer (beginning with high bits so the handler can simply shift right to check them in reverse order)
enableDefer.add(PtNumber(DataType.UBYTE, (1 shl (defers.size-1 - deferIndex)).toDouble(), defer.position))
enableDefer.parent = sub
sub.children[idx] = enableDefer
}
}
return defersPerSub
}
private fun integrateDefers(program: PtProgram, st: SymbolTable) {
val jumpsToAugment = mutableListOf<PtJump>()
private fun integrateDefers(subdefers: Map<PtSub, List<PtDefer>>, program: PtProgram, st: SymbolTable) {
val jumpsAndCallsToAugment = mutableListOf<PtNode>()
val returnsToAugment = mutableListOf<PtReturn>()
val subEndsToAugment = mutableListOf<PtSub>()
val callsToAugment = mutableListOf<PtFunctionCall>()
walkAst(program) { node, _ ->
when(node) {
is PtFunctionCall -> {
if(node.name.startsWith("sys.exit"))
callsToAugment.add(node)
}
is PtJump -> {
if(node.identifier!=null) {
val stNode = st.lookup(node.identifier!!.name)!!
val targetSub = stNode.astNode.definingSub()
if(targetSub!=node.definingSub())
jumpsToAugment.add(node)
if(node !is PtProgram && node.definingSub() in subdefers) {
when (node) {
is PtReturn -> returnsToAugment.add(node)
is PtFunctionCall -> {
if (node.name.startsWith("sys.exit"))
jumpsAndCallsToAugment.add(node)
}
is PtJump -> {
if (node.identifier != null) {
val stNode = st.lookup(node.identifier!!.name)!!
val targetSub = stNode.astNode.definingSub()
if (targetSub != node.definingSub())
jumpsAndCallsToAugment.add(node)
}
}
is PtSub -> {
val lastStmt = node.children.lastOrNull { it !is PtDefer }
if (lastStmt != null && lastStmt !is PtReturn && lastStmt !is PtJump)
subEndsToAugment.add(node)
}
else -> {}
}
is PtReturn -> returnsToAugment.add(node)
is PtSub -> {
val lastStmt = node.children.lastOrNull { it !is PtDefer }
if(lastStmt != null && lastStmt !is PtReturn && lastStmt !is PtJump)
subEndsToAugment.add(node)
}
else -> {}
}
}
fun invokedeferbefore(node: PtNode) {
val defer = node.definingSub()!!.children.singleOrNull { it is PtDefer }
if (defer != null) {
val idx = node.parent.children.indexOf(node)
val invokedefer = PtBuiltinFunctionCall("invoke_defer", true, false, DataType.UNDEFINED, node.position)
node.parent.add(idx, invokedefer)
}
val idx = node.parent.children.indexOf(node)
val invokedefer = PtFunctionCall(node.definingSub()!!.scopedName+"."+invokeDefersRoutineName, true, DataType.UNDEFINED, node.position)
node.parent.add(idx, invokedefer)
}
fun notComplex(value: PtExpression): Boolean = when(value) {
is PtAddressOf -> value.arrayIndexExpr == null || notComplex(value.arrayIndexExpr!!)
is PtBuiltinFunctionCall -> {
@ -96,21 +125,13 @@ private fun integrateDefers(program: PtProgram, st: SymbolTable) {
else -> false
}
// calls (sys.exit) exits
for(call in callsToAugment) {
// jumps and calls (sys.exit) exits
for(call in jumpsAndCallsToAugment) {
invokedeferbefore(call)
}
// jump exits
for(exit in jumpsToAugment) {
invokedeferbefore(exit)
}
// return exits
for(ret in returnsToAugment) {
val defer = ret.definingSub()!!.children.singleOrNull { it is PtDefer }
if(defer == null)
continue
val value = ret.value
if(value==null || notComplex(value)) {
invokedeferbefore(ret)
@ -123,7 +144,7 @@ private fun integrateDefers(program: PtProgram, st: SymbolTable) {
newRet.add(popCall)
val group = PtNodeGroup()
group.add(pushCall)
group.add(PtBuiltinFunctionCall("invoke_defer", true, false, DataType.UNDEFINED, ret.position))
group.add(PtBuiltinFunctionCall(invokeDefersRoutineName, true, false, DataType.UNDEFINED, ret.position))
group.add(newRet)
group.parent = ret.parent
val idx = ret.parent.children.indexOf(ret)
@ -137,53 +158,41 @@ private fun integrateDefers(program: PtProgram, st: SymbolTable) {
val idx = sub.children.indexOfLast { it !is PtDefer }
val ret = PtReturn(sub.position)
sub.add(idx+1, ret)
val invokedefer = PtBuiltinFunctionCall("invoke_defer", true, false, DataType.UNDEFINED, sub.position)
val invokedefer = PtBuiltinFunctionCall(invokeDefersRoutineName, true, false, DataType.UNDEFINED, sub.position)
sub.add(idx+1, invokedefer)
}
}
}
/* start of new defer implementation:
private fun integrateDefers(program: PtProgram, errors: IErrorReporter) {
val defersPerSub = mutableMapOf<PtSub, MutableList<PtDefer>>().withDefault { mutableListOf() }
walkAst(program) { node, _ ->
if(node is PtDefer) {
val scope = node.definingSub()!!
val defers = defersPerSub.getValue(scope)
defers.add(node)
defersPerSub[scope] = defers
for( (sub, defers) in subdefers) {
// create the routine that calls the enabled defers in reverse order
val defersRoutine = PtSub(invokeDefersRoutineName, emptyList(), null, Position.DUMMY)
defersRoutine.parent=sub
for((idx, defer) in defers.reversed().withIndex()) {
val shift = PtAugmentedAssign(">>=", Position.DUMMY)
shift.add(PtAssignTarget(false, sub.position).also {
it.add(PtIdentifier(sub.scopedName+"."+maskVarName, DataType.UBYTE, sub.position))
})
shift.add(PtNumber(DataType.UBYTE, 1.0, sub.position))
defersRoutine.add(shift)
val skiplabel = "prog8_defer_skip_${idx+1}"
val branchcc = PtConditionalBranch(BranchCondition.CC, Position.DUMMY)
branchcc.add(PtNodeGroup().also {
it.add(PtJump(PtIdentifier(defersRoutine.scopedName+"."+skiplabel, DataType.UNDEFINED, Position.DUMMY), null, Position.DUMMY))
})
branchcc.add(PtNodeGroup())
defersRoutine.add(branchcc)
for(c in defer.children) {
defersRoutine.add(c)
}
defersRoutine.add(PtLabel(skiplabel, Position.DUMMY))
}
}
// val printMask = PtFunctionCall("txt.print_ubbin", true, DataType.UNDEFINED, Position.DUMMY)
// printMask.add(PtIdentifier(sub.scopedName+"."+maskVarName, DataType.UBYTE, Position.DUMMY))
// printMask.add(PtBool(true, Position.DUMMY))
// defersRoutine.add(printMask)
val maskVarName = "prog8_defers_mask"
for((sub, defers) in defersPerSub) {
if(defers.isEmpty())
continue
if (defers.size > 8) {
errors.err("can have no more than 8 defers per subroutine", sub.position)
return
}
val deferVariable = PtVariable(maskVarName, DataType.UBYTE, ZeropageWish.NOT_IN_ZEROPAGE, null, null, sub.position)
sub.add(0, deferVariable)
for((deferIndex, defer) in defers.withIndex()) {
val idx = defer.parent.children.indexOf(defer)
val enableDefer = PtAugmentedAssign("|=", defer.position)
val target = PtAssignTarget(true, defer.position)
target.add(PtIdentifier(sub.scopedName+"."+maskVarName, DataType.UBYTE, defer.position))
enableDefer.add(target)
enableDefer.add(PtNumber(DataType.UBYTE, (1 shl deferIndex).toDouble(), defer.position))
sub.add(idx, enableDefer)
}
defersRoutine.add(PtReturn(Position.DUMMY))
sub.add(defersRoutine)
}
}
*/

View File

@ -6,7 +6,6 @@ why is 0 as value stored as null in symboltablemaker?
are variables initialized with 0 reset to 0 with an assignment? WHY is the BSS area then cleared with memset? shouldn't be necessary?
- defers that haven't been reached yet should not be executed (how will we do this? some kind of runtime support needed? refcount or bitmask, not a boolean var per defer that would be wasteful)
- unit test for defer
- describe defer in the manual

View File

@ -4,25 +4,8 @@
%zeropage basicsafe
main {
sub start() {
ubyte @shared c=99
if c>100
cx16.r0L++
cx16.r0L = if (c>100) 2 else (3)
txt.print_ub(if (c>100) 2 else 3)
txt.nl()
txt.print_ub(if (c<100) 6 else 7)
txt.nl()
float @shared fl=99.99
floats.print(if (c>100) 2.22 else 3.33)
txt.nl()
floats.print(if (c<100) 6.66 else 7.77)
txt.nl()
uword res1 = allocate(111)
defer deallocate(res1)
uword res2 = allocate(222)
@ -37,6 +20,11 @@ main {
}
sub allocate(uword arg) -> uword {
; if arg==222
; return 0
txt.print("allocate ")
txt.print_uw(4000+arg)
txt.nl()
return 4000+arg
}