added %jmptable

This commit is contained in:
Irmen de Jong 2025-02-09 15:02:59 +01:00
parent 8d2410622c
commit efd41260f2
19 changed files with 112 additions and 40 deletions

View File

@ -171,6 +171,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
}
is PtDefer -> "<defer>"
is PtIfExpression -> "<ifexpr>"
is PtJmpTable -> "<jmptable>"
}
}

View File

@ -193,3 +193,6 @@ class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
class PtDefer(position: Position): PtNode(position), IPtStatementContainer
class PtJmpTable(position: Position) : PtNode(position) // contains only PtIdentifier nodes

View File

@ -606,6 +606,7 @@ class AsmGen6502Internal (
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 PtJmpTable -> translate(stmt)
is PtNop -> {}
else -> throw AssemblyError("missing asm translation for $stmt")
}
@ -982,6 +983,14 @@ $repeatLabel""")
out(stmt.name)
}
private fun translate(stmt: PtJmpTable) {
out(" ; jumptable")
for(name in stmt.children) {
out(" jmp ${asmSymbolName((name as PtIdentifier).name)}")
}
out(" ; end jumptable")
}
private fun translate(stmt: PtConditionalBranch) {
if(stmt.trueScope.children.isEmpty() && stmt.falseScope.children.isNotEmpty())
throw AssemblyError("only else part contains code, shoud have been switched already")

View File

@ -1854,6 +1854,14 @@ class IRCodeGen(
is PtLabel -> {
irBlock += IRCodeChunk(child.name, null)
}
is PtJmpTable -> {
irBlock += IRCodeChunk(null, null).also {
for(addr in child.children) {
addr as PtIdentifier
it += IRInstruction(Opcode.JUMP, labelSymbol = addr.name)
}
}
}
else -> TODO("weird block child node $child")
}
}

View File

@ -101,7 +101,7 @@ class UnusedCodeRemover(private val program: Program,
}
if (callgraph.unused(block)) {
if (block.statements.any { it !is VarDecl || it.type == VarDeclType.VAR } && "ignore_unused" !in block.options()) {
if (!block.statements.any { it is Subroutine && it.hasBeenInlined })
if (!block.statements.any { it is Subroutine && !it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}
if (!block.statements.any { it is Subroutine && it.hasBeenInlined }) {

View File

@ -1052,6 +1052,15 @@ internal class AstChecker(private val program: Program,
if(directive.args.size!=1 || directive.args[0].string !in allowedEncodings)
err("invalid encoding directive, expected one of $allowedEncodings")
}
"%jmptable" -> {
for(arg in directive.args) {
val target = directive.definingScope.lookup(arg.string!!.split('.'))
if(target==null)
errors.err("undefined symbol: ${arg.string}", arg.position)
else if (target !is Subroutine)
errors.err("jmptable entry can only be a subroutine: ${arg.string}", arg.position)
}
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
}
super.visit(directive)

View File

@ -211,11 +211,22 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
}
}
}
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
val src = srcBlock.definingModule.source
val block = PtBlock(srcBlock.name, srcBlock.isInLibrary, src,
PtBlock.Options(srcBlock.address, forceOutput, noSymbolPrefixing, veraFxMuls, ignoreUnused),
srcBlock.position)
for(directive in directives.filter { it.directive == "%jmptable" }) {
val table = PtJmpTable(directive.position)
directive.args.forEach {
table.add(PtIdentifier(it.string!!, DataType.forDt(BaseDataType.UNDEFINED), it.position))
}
block.add(table)
}
makeScopeVarsDecls(vardecls).forEach { block.add(it) }
for (stmt in statements)
block.add(transformStatement(stmt))

View File

@ -440,8 +440,14 @@ private fun DatatypeContext.toAst(): BaseDataType {
private fun ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
internal fun DirectiveContext.toAst() : Directive =
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
internal fun DirectiveContext.toAst() : Directive {
if(directivenamelist() != null) {
val identifiers = directivenamelist().scoped_identifier().map { DirectiveArg(it.text, null, it.toPosition()) }
return Directive(directivename.text, identifiers, toPosition())
}
else
return Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
}
private fun DirectiveargContext.toAst() : DirectiveArg {
val str = stringliteral()

View File

@ -111,6 +111,7 @@ data class Directive(val directive: String, val args: List<DirectiveArg>, overri
// "%breakpoint",
// "%encoding",
// "%import",
// "%jmptable",
// "%launcher",
// "%option",
// "%output",

View File

@ -62,6 +62,17 @@ class CallGraph(private val program: Program) : IAstVisitor {
importedBy[importedModule] = importedBy.getValue(importedModule) + thisModule
}
}
else if (directive.directive == "%jmptable") {
for(arg in directive.args) {
val scopedName = arg.string!!.split('.')
val target = directive.definingScope.lookup(scopedName)
if(target is Subroutine) {
val identifier = IdentifierReference(scopedName, arg.position)
identifier.linkParents(directive)
visit(identifier)
}
}
}
super.visit(directive)
}
@ -107,8 +118,11 @@ class CallGraph(private val program: Program) : IAstVisitor {
override fun visit(identifier: IdentifierReference) {
val target = identifier.targetStatement(program)
if(target!=null)
if(target!=null) {
allIdentifiersAndTargets.add(identifier to target)
if(target is Subroutine)
notCalledButReferenced += target
}
// if it's a scoped identifier, the subroutines in the name are also referenced!
val scope = identifier.definingScope

View File

@ -362,6 +362,18 @@ Directives
You can import modules one at a time, and importing a module more than once has no effect.
.. data:: %jmptable ( lib.routine1, lib.routine2, ... )
Level: block.
This builds a compact "jump table" meant to be used in libraries.
You can put the elements of the table on different lines if you wish.
It outputs a sequence of JMP machine code instructions jumping to each
of the given subroutines in the jmptable list::
jmp lib.routine1
jmp lib.routine2
...
.. data:: %launcher <type>
Level: module.

View File

@ -1,11 +1,6 @@
TODO
====
- diskio has a problem when running certain code when a not-connected drive number is selected, and when that is run in a hiram bank
(this is not a prog8 problem, see https://discord.com/channels/547559626024157184/629863245934755860/1336805196634001438)
- for creating libraries, something like %jmptable( block.func1, block.func2, ... ) could be useful to create a compact jump table, and possibly generating extsub definitions as well. Problem: directives cannot span multiple lines atm.
- Make neo and atari targets external via configs? They are very bare bones atm so easier to contribute to if they're configurable externally? What about the pet32 target
- add paypal donation button as well?

View File

@ -19,17 +19,17 @@
main {
; Create a jump table as first thing in the library.
uword[] @shared @nosplit jumptable = [
; NOTE: the compiler has inserted a single JMP instruction at the start
; of the 'main' block, that jumps to the start() routine.
; This is convenient because the rest of the jump table simply follows it,
; making the first jump neatly be the required initialization routine
; for the library (initializing variables and BSS region).
; Btw, $4c = opcode for JMP.
$4c00, &fileselector.configure,
$4c00, &fileselector.configure_appearance,
$4c00, &fileselector.select,
]
; NOTE: the compiler has inserted a single JMP instruction at the start
; of the 'main' block, that jumps to the start() routine.
; This is convenient because the rest of the jump table simply follows it,
; making the first jump neatly be the required initialization routine
; for the library (initializing variables and BSS region).
; Think of it as the implicit first entry of the jump table.
%jmptable (
fileselector.configure,
fileselector.configure_appearance,
fileselector.select
)
sub start() {
; has to remain here for initialization

View File

@ -33,11 +33,11 @@ fselector {
extsub $a000 = init() clobbers(A)
; what entry types should be displayed (default=all)
extsub $a004 = config(ubyte drivenumber @A, ubyte types @Y) clobbers(A)
extsub $a003 = config(ubyte drivenumber @A, ubyte types @Y) clobbers(A)
; configure the position and appearance of the dialog
extsub $a008 = config_appearance(ubyte column @R0, ubyte row @R1, ubyte max_entries @R2, ubyte normalcolors @R3, ubyte selectedcolors @R4) clobbers(A)
extsub $a006 = config_appearance(ubyte column @R0, ubyte row @R1, ubyte max_entries @R2, ubyte normalcolors @R3, ubyte selectedcolors @R4) clobbers(A)
; show the file selector dialog. Normal pattern would be "*" to include everything. Returns the selected entry name, or 0 if error or nothing selected.
extsub $a00c = select(str pattern @AY) clobbers(X) -> uword @AY
extsub $a009 = select(str pattern @AY) clobbers(X) -> uword @AY
}

View File

@ -7,14 +7,19 @@
main {
; Create a jump table as first thing in the library.
uword[] @shared @nosplit jumptable = [
; NOTE: the compiler has inserted a single JMP instruction at the start of the 'main' block, that jumps to the start() routine.
; This is convenient because the rest of the jump table simply follows it,
; making the first jump neatly be the required initialization routine for the library (initializing variables and BSS region).
; btw, $4c = opcode for JMP.
$4c00, &library.func1,
$4c00, &library.func2,
]
; uword[] @shared @nosplit jumptable = [
; ; NOTE: the compiler has inserted a single JMP instruction at the start of the 'main' block, that jumps to the start() routine.
; ; This is convenient because the rest of the jump table simply follows it,
; ; making the first jump neatly be the required initialization routine for the library (initializing variables and BSS region).
; ; btw, $4c = opcode for JMP.
; $4c00, &library.func1,
; $4c00, &library.func2,
; ]
%jmptable (
library.func1,
library.func2,
)
sub start() {
; has to be here for initialization

View File

@ -406,11 +406,7 @@ class IRBlock(
operator fun plusAssign(sub: IRAsmSubroutine) { children += sub }
operator fun plusAssign(asm: IRInlineAsmChunk) { children += asm }
operator fun plusAssign(binary: IRInlineBinaryChunk) { children += binary }
operator fun plusAssign(irCodeChunk: IRCodeChunk) {
// this is for a separate label in the block scope. (random code statements are not allowed)
require(irCodeChunk.isEmpty() && irCodeChunk.label!=null)
children += irCodeChunk
}
operator fun plusAssign(irCodeChunk: IRCodeChunk) { children += irCodeChunk }
fun isEmpty(): Boolean = children.isEmpty() || children.all { it.isEmpty() }
fun isNotEmpty(): Boolean = !isEmpty()

View File

@ -137,10 +137,12 @@ unconditionaljump : 'goto' expression ;
directive :
directivename=('%output' | '%launcher' | '%zeropage' | '%zpreserved' | '%zpallowed' | '%address' | '%memtop' | '%import' |
'%breakpoint' | '%asminclude' | '%asmbinary' | '%option' | '%encoding' | '%align' )
(directivearg? | directivearg (',' directivearg)*)
'%breakpoint' | '%asminclude' | '%asmbinary' | '%option' | '%encoding' | '%align' | '%jmptable' )
(directivenamelist | (directivearg? | directivearg (',' directivearg)*))
;
directivenamelist: '(' EOL? scoped_identifier (',' EOL? scoped_identifier)* ','? EOL?')' ;
directivearg : stringliteral | identifier | integerliteral ;
vardecl: datatype (arrayindex | ARRAYSIG)? TAG* identifier (',' identifier)* ;

View File

@ -12,7 +12,7 @@
<option name="HAS_STRING_ESCAPES" value="true" />
</options>
<keywords keywords="&amp;;&amp;&lt;;&amp;&gt;;-&gt;;@;alias;and;as;asmsub;break;clobbers;continue;do;downto;else;extsub;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;step;sub;to;true;unroll;until;when;while;xor;~" ignore_case="false" />
<keywords2 keywords="%address;%align;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%launcher;%memtop;%option;%output;%zeropage;%zpallowed;%zpreserved;@align64;@alignpage;@alignword;@bank;@dirty;@nosplit;@nozp;@requirezp;@shared;@split;@zp;atascii:;c64os:;cp437:;default:;iso16:;iso5:;iso:;kata:;petscii:;sc:" />
<keywords2 keywords="%address;%align;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%jmptable;%launcher;%memtop;%option;%output;%zeropage;%zpallowed;%zpreserved;@align64;@alignpage;@alignword;@bank;@dirty;@nosplit;@nozp;@requirezp;@shared;@split;@zp;atascii:;c64os:;cp437:;default:;iso16:;iso5:;iso:;kata:;petscii:;sc:" />
<keywords3 keywords="bool;byte;const;float;long;str;ubyte;uword;void;word" />
<keywords4 keywords="abs;bmx;call;callfar;callfar2;cbm;clamp;cmp;conv;cx16;defer;divmod;floats;len;lsb;lsw;math;max;memory;min;mkword;msb;msw;peek;peekf;peekw;poke;pokef;pokew;psg;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sqrt;strings;sys;txt;verafx" />
</highlighting>

View File

@ -25,7 +25,7 @@
<Keywords name="Folders in comment, middle"></Keywords>
<Keywords name="Folders in comment, close"></Keywords>
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte bool&#x000D;&#x000A;long word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared split nosplit requirezp nozp</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%ir&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%align&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%encoding&#x000D;&#x000A;%import&#x000D;&#x000A;%memtop&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved&#x000D;&#x000A;%zpallowed</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%ir&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%align&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%encoding&#x000D;&#x000A;%import&#x000D;&#x000A;%jmptable&#x000D;&#x000A;%memtop&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved&#x000D;&#x000A;%zpallowed</Keywords>
<Keywords name="Keywords3">inline sub asmsub extsub&#x000D;&#x000A;clobbers&#x000D;&#x000A;asm&#x000D;&#x000A;if&#x000D;&#x000A;when else&#x000D;&#x000A;if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z&#x000D;&#x000A;for in step do while repeat unroll&#x000D;&#x000A;break continue return goto</Keywords>
<Keywords name="Keywords4">alias abs call callfar callfar2 clamp cmp defer divmod len lsb lsw lsl lsr memory mkword min max msb msw peek peekw peekf poke pokew pokef rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sqrtw</Keywords>
<Keywords name="Keywords5">true false&#x000D;&#x000A;not and or xor&#x000D;&#x000A;as to downto |&gt;</Keywords>