Compare commits

...
622 Commits
Author SHA1 Message Date
Irmen de Jong 7ba2ee9857 added named struct field initializers 2026-09-14 00:56:22 +02:00
Irmen de Jong a025d68291 added %assert expression, "message" 2026-09-13 23:04:56 +02:00
Irmen de Jong 81794aded3 use Mordant library to make some terminal output more sophisticated
this library is already used by the Clikt option parser library so no new dependency
2026-09-13 21:28:32 +02:00
Irmen de Jong 9621552056 add 'arena' allocator library
6502 targets use uword sizes with no alignment overhead; m68k/virtual use
long sizes with even-address alignment. Updated the six pointer examples that used to have their own arena implementation.
2026-09-13 05:43:57 +02:00
Irmen de Jong 5685a1f229 6502: tighter for loop code for word and long loop vars 2026-09-13 04:29:59 +02:00
Irmen de Jong b014c78c94 m68k: better error when trying to use cx16 virtual register, those don't exist anymore 2026-09-13 04:28:25 +02:00
Irmen de Jong ca729d279e update history and syntax files 2026-09-12 23:14:50 +02:00
Irmen de Jong 3b0faa749a Merge branch 'master' into structured-IR
Port end-exclusive for-loop codegen and M4 loop-counter peephole to the
structured operand API (IRInstructions factories, memory?.symbolName,
labelTarget, requireDest/requireSrcA).
2026-09-12 00:48:30 +02:00
Irmen de Jong 22a2f8cbc5 IR: end-exclusive increment+compare for step +-1 for-loops; fix DECM/BSTPOS on VM 2026-09-12 00:07:02 +02:00
Irmen de Jong 468850bc47 structured IR 2026-09-11 20:20:40 +02:00
Irmen de Jong 20374f4059 Merge branch 'v12maintenance'
# Conflicts:
#	compilerAst/src/prog8/ast/antlr/Antlr2KotlinVisitor.kt
#	compilerAst/src/prog8/ast/statements/AstStatements.kt
#	examples/test.p8
2026-09-11 19:37:10 +02:00
Irmen de Jong ceffa4d83d todos 2026-09-11 19:25:43 +02:00
Irmen de Jong af14562e2b allow struct field array sizes to be constant expressions
fixes #240
2026-09-11 01:38:18 +02:00
Irmen de Jong dbba6a94fa Merge branch 'v12maintenance'
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt
#	compiler/src/prog8/compiler/astprocessing/AstIdentifiersChecker.kt
#	compiler/test/ast/TestVariousCompilerAst.kt
#	compiler/test/codegeneration/TestExecution6502.kt
#	examples/test.p8
#	gradle.properties
2026-09-10 21:45:47 +02:00
Irmen de Jong 302e4bdf8e m68k targets are experimental not for production code use 2026-09-10 20:41:00 +02:00
Irmen de Jong ccc26a0eb2 Fix alias targets to enum members
Defer alias error reporting when the target is a not-yet-desugared
enum member. Also reject aliases that target an enum itself.

fixes #239
2026-09-10 19:20:02 +02:00
Irmen de Jong e017c541bb proper error message when using a builtin function name as block name. Also for some keywords if the parser allows. Fixes #236 2026-09-10 19:20:02 +02:00
Irmen de Jong 3d5a7795d0 make set of keywords (for name restriction test) dynamic 2026-09-10 19:20:02 +02:00
Irmen de Jong af06fdfffc fix more 6502-only struct pointer-field bugs; add indexed in-place support
fixes #237
2026-09-10 19:20:02 +02:00
Irmen de Jong e811ecdbde fix 6502 struct pointer-field array indexing 2026-09-09 23:47:09 +02:00
Irmen de Jong 4c65049817 proper error message when using a builtin function name as block name 2026-09-09 04:05:31 +02:00
Irmen de Jong a7cbc2c5bf Review fixes: pointer read desugaring and poke->assignment optimizer 2026-09-09 02:31:21 +02:00
Irmen de Jong d51ca62431 Fix poke->assignment optimizer dropping AddressOf index
StatementOptimizer rewrote pokel(&arena[1], x) to arena = x, ignoring
AddressOf.arrayIndex (and dereference), which miscompiled implicit
indexed-base struct field writes such as arena[1].s = X. All four
poke/pokew/pokel/pokef rewrites now require a plain variable address.
2026-09-09 02:05:20 +02:00
Irmen de Jong 9ac60eec40 fix some more pointer syntax related issues 2026-09-09 01:33:43 +02:00
Irmen de Jong c55b16f4cb todo 2026-09-09 01:01:16 +02:00
Irmen de Jong bd0457f262 Allow const expressions for 2D array dimensions (not just numeric literals) 2026-09-08 23:13:54 +02:00
Irmen de Jong ce2b298d6f code cleanups 2026-09-08 22:36:54 +02:00
Irmen de Jong cf7afaf77a update libraries, update to kotlin 2.4.20 2026-09-08 21:41:53 +02:00
Irmen de Jong 54507ad9b3 Merge branch 'v12maintenance'
# Conflicts:
#	gradle.properties
2026-09-07 19:19:52 +02:00
Irmen de Jong 30435b6778 version 12.3.3 2026-09-07 19:18:15 +02:00
Irmen de Jong 619a215a87 fix test size expectation 2026-09-07 19:14:58 +02:00
Irmen de Jong c12e27dacc cx16: added verafx-accelerated line routine, added cobramk3 example that uses it next to the cpu only version 2026-09-06 22:51:12 +02:00
Irmen de Jong db32b81d7a m68k: d0 caching 2026-09-06 20:32:16 +02:00
Irmen de Jong 687d86d36e added amiga1200 target (68020 + FPU) 2026-09-06 18:25:15 +02:00
Irmen de Jong e074fe614f avoid extremely long array data source lines, and redundant label comments 2026-09-06 16:18:31 +02:00
Irmen de Jong bbfb09e467 IR: uniform traversal 2026-09-05 00:06:30 +02:00
Irmen de Jong 1358a535e3 Fix inliner crash when inlining a sub that assigns to its own parameter
Also error on a variable declaration directly followed by another statement on the same line.

fixes #235
2026-09-04 22:13:11 +02:00
Irmen de Jong bc62b2de81 IR: fix CONCAT register direction 2026-09-04 19:42:31 +02:00
Irmen de Jong 58f544fe14 Implement counted-loop IR (IRLoopChunk)
Replace the early lowering of constant repeat/for loops into p8_regfile
counter sequences with a first-class IRLoopChunk that carries the trip
count and body. This removes the fragile REGFILE-DEPENDENT m68k dbra
peephole and gives each backend explicit control.
2026-09-04 00:01:00 +02:00
Irmen de Jong 7dc05688bf Add peephole optimization to fold array[variable+const] index offsets
Adds three new passes to the 6502 assembly optimizer:

- optimizeFoldIndexOffset (byte/bool arrays)
- optimizeFoldIndexOffsetSplitWord (split word arrays)
- optimizeFoldIndexOffsetNonSplitWord (@nosplit word arrays)

fixes github issue #234
2026-09-03 21:36:29 +02:00
Irmen de Jong aac7d409dc split AssignmentAsmGen into 3 helper objects to reduce massive file size 2026-09-03 03:39:29 +02:00
Irmen de Jong 0771105694 fix sorting module on virtual target 2026-09-03 03:39:07 +02:00
Irmen de Jong 9a461628f2 Merge branch 'v12maintenance' 2026-09-02 19:14:57 +02:00
Irmen de Jong c8f309307f fix inlining bug: subroutines with register-mapped parameters cannot be inlined 2026-09-02 19:10:21 +02:00
Irmen de Jong b702ea50da no longer mangle inline asm that uses pipe operator (in other spots that is used as an instruction separator...) 2026-09-02 02:02:53 +02:00
Irmen de Jong b0f1f655c3 codeGenIntermediate: fold struct-array field access to single LOADX/STOREX
Replace arr[idx].field (arr:Struct[Sz], idx variable) from
EXT+MUL #Sz + ADDR + ADD #fieldOff + LOADI/STOREI (5 ops) to
single indexed op: LOADX/STOREX arr+fieldOff,S=Sz and
STOREZX for zero. Reuses existing ,S=N + +off encoding.
2026-09-02 01:35:30 +02:00
Irmen de Jong 59c41ad129 ir: add scaled indexing (S=) to LOADX/STOREX/STOREZX
Remove byte-offset pre-scaling from IR generation and encode
element index + explicit scale in the IR.  Effective address is
base + index*scale + disp (disp = symbolOffset).
2026-09-02 00:34:23 +02:00
Irmen de Jong 60eeaba791 streamline IR optimization proposals 2026-09-01 23:15:03 +02:00
Irmen de Jong 30266c1b92 Merge branch 'v12maintenance'
# Conflicts:
#	gradle.properties
2026-09-01 22:22:39 +02:00
Irmen de Jong 05ba1237dc fix %asm line grouping that tripped the asm optimizer to think stuff was one line where it wasn't 2026-09-01 22:04:33 +02:00
Irmen de Jong 9d537ab3c0 warn when %zpreserve/%zpallowed excludes fixed zeropage scratch locations 2026-09-01 04:21:49 +02:00
Irmen de Jong 1ba08ec6ec make -dumpvars work on all compilation targets 2026-09-01 03:51:13 +02:00
Irmen de Jong 199c3e4efb Merge branch 'new-codegens' 2026-08-30 23:54:10 +02:00
Irmen de Jong a7c035f4d1 attempt to fix github runner OOM 2026-08-30 23:44:05 +02:00
Irmen de Jong 05cbe74a60 attempt to fix github runner OOM 2026-08-30 23:04:47 +02:00
Irmen de Jong d652d7a924 Merge branch 'new-codegens'
# Conflicts:
#	gradle.properties
2026-08-30 22:47:38 +02:00
Irmen de Jong 739fb6dc42 release 12.3.2 2026-08-30 22:39:01 +02:00
Irmen de Jong 9c53791ef4 m68k: optimize small memcopies to inline copy 2026-08-30 21:35:48 +02:00
Irmen de Jong 420a9b7249 m68k: optimize load of constant args into CPU registers for subroutine call 2026-08-30 19:20:12 +02:00
Irmen de Jong 9bf8318b1c Support arrays of struct instances
- Parser: parse `StructType[N] var` via elementToArray() for
  STRUCT_INSTANCE (Prog8ANTLR.g4, Antlr2KotlinVisitor)
- Desugar: struct-instance bases via addressOfStructInstance(),
  whole-struct `a[i]=a[j]` -> sys.memcopy, inferred `[[...]]`
  and explicit `Struct : [...]` (without ^^) for value arrays;
  reject `^^Struct : [...]` in instance arrays
- 6502: computeStructArrayElementAddress() with 16-bit
  index*structSize scaling (AssignmentAsmGen, AsmGen)
- IR/VM/m68k/new6502: STRUCTDEFS round-trip, loaders, gens
- AstChecker/IdentifiersChecker: reject struct fields that are
  arrays of structs; fix isArray guards
- Docs: structpointers.rst/variables.rst, todo.rst, examples/test.p8
- Tests: TestArraysOfStructs (pointer vs value init, IR round-trip,
  16-bit strides)
2026-08-30 18:24:14 +02:00
Irmen de Jong 94822b8662 Add arrays of struct instances (StructType[N] vars)
Parser: accept `Type[N] name` between type and identifier
(Prog8ANTLR.g4, Antlr2KotlinVisitor) via elementToArray()
for STRUCT_INSTANCE; CodeDesugarer: handle struct-instance
bases, addressOfStructInstance(), whole-struct memcopy
(points[a]=points[b]), and fix variable-index 16-bit scaling
in 6502 AssignmentAsmGen.computeStructArrayElementAddress();
IR: preserve STRUCTDEFS round-trip, VM loader support;
m68k/new6502/intermediate backends; docs + todo updated.
Limitations: not as struct fields (AstChecker/
AstIdentifiersChecker error) nor as sub params.
2026-08-30 14:14:49 +02:00
Irmen de Jong b0d50c2a68 preserve block comments in compiler AST and AST output 2026-08-30 02:33:30 +02:00
Irmen de Jong deae2e6422 Unify variable and memory slab relocation with vars address configuration 2026-08-29 01:17:44 +02:00
Irmen de Jong c8d29e6dc9 ir: more IR peephole optimizations 2026-08-29 00:29:50 +02:00
Irmen de Jong a7b14c2931 ir: more optimized inplace array assignments 2026-08-28 23:38:54 +02:00
Irmen de Jong a14a8f4e62 ir: deduplicate index canonicalization and strengthen regression tests 2026-08-28 23:00:51 +02:00
Irmen de Jong cf50372515 ir: canonicalize LOADX/STOREX/STOREZX index width and remove guessed-type workaround 2026-08-28 22:23:55 +02:00
Irmen de Jong da0df8f133 amiga: use defer for resource freeing, change custom.grab_system and restore_system value storage so they are now regular subroutines instead of inline 2026-08-28 21:27:02 +02:00
Irmen de Jong 27070f31e8 Merge branch 'master' into new-codegens
# Conflicts:
#	codeCore/src/prog8/code/core/DataTypes.kt
#	docs/source/todo.rst
2026-08-28 05:23:30 +02:00
Irmen de Jong aa3f8e3038 fix daemon error 2026-08-28 05:14:12 +02:00
Irmen de Jong 277e9c09bb making defer program-wide: also unwinds at program exit()/return from start() 2026-08-28 05:10:09 +02:00
Irmen de Jong 70521ce301 amiga: fix modplay file close 2026-08-27 23:05:13 +02:00
Irmen de Jong 93b5faa038 m68k: revert LSIGB/LSIGW direct p8_regfile loads - caused code growth 2026-08-27 19:49:21 +02:00
Irmen de Jong f33814a594 m68k: fuse LOADX+JUMPI/CALLI in asm peephole and document %jmptable as 6502-only 2026-08-27 04:50:07 +02:00
Irmen de Jong 986ae35821 m68k: optimize LSIGB/LSIGW to direct p8_regfile byte loads 2026-08-27 03:29:56 +02:00
Irmen de Jong 937b520838 core: fix POINTER equalsSize target-aware for 32-bit 2026-08-27 03:15:57 +02:00
Irmen de Jong 250ddda11b ir,m68k: add LOADP_INC/STOREP_INC for pointer post-inc (a0)+ 2026-08-27 02:57:54 +02:00
Irmen de Jong bde52cc134 ir: fix byte->long/pointer extension for 32-bit targets (single-type EXTL) 2026-08-26 23:55:17 +02:00
Irmen de Jong 8e739febf0 ir: m68k status-flag fusion - skip redundant CMPI #0 on ops that set Z 2026-08-26 23:41:36 +02:00
Irmen de Jong 3fcb4b4262 ir: canonicalize LOADX index width, 2026-08-26 23:18:06 +02:00
Irmen de Jong 56643f43df tweak m68k tests and docs 2026-08-26 22:31:05 +02:00
Irmen de Jong a9cad9b51d fix some problems in several examples 2026-08-26 00:49:02 +02:00
Irmen de Jong 8647b136bd 6502: fix constant byte store via pointer+const offset storing garbage
fixes regression from commit 102992911
2026-08-25 23:59:41 +02:00
Irmen de Jong 4179f2c571 disable RMB-menus, doc updates 2026-08-25 03:31:15 +02:00
Irmen de Jong 02a118ec0e deref assignment targets: use target pointer width for address temps and IR casts 2026-08-24 23:38:45 +02:00
Irmen de Jong 561f31329d support pointer assignment targets like (expr as ^^Struct).field = v and func().field = v (grammar + CodeDesugarer poke desugar, IR pointer-cast widening, tests/docs) 2026-08-24 23:00:23 +02:00
Irmen de Jong 2bbbacc46e normal error message instead of crash when typed pointer arrays are used as struct field (currently not supported by the compiler) 2026-08-24 19:21:30 +02:00
Irmen de Jong c3920de251 update to new ksim65 version 2026-08-24 00:25:01 +02:00
Irmen de Jong 14bf07907b amiga: tweak modplay and ptplayer library to be able to supply playback feedback 2026-08-23 23:00:33 +02:00
Irmen de Jong c657935f10 m68k: fix passing 0 in m68k address register codegen 2026-08-23 22:30:50 +02:00
Irmen de Jong c08def79ae Merge branch 'master' into new-codegens 2026-08-23 21:04:23 +02:00
Irmen de Jong 296295011e Fix 6502 indexed pointer array store correctness bugs / missing code gen implementations 2026-08-23 20:54:32 +02:00
Irmen de Jong de20c09790 fix missing array bounds check for struct field arrays 2026-08-23 18:42:57 +02:00
Irmen de Jong 4199ab4200 fix broken unit test dependency on vasm 2026-08-23 16:07:27 +02:00
Irmen de Jong fe6850816c m68k: restore zero-extension for ADDR/SUBR widening and byte array indices
this broke in d978be2f9d
2026-08-23 15:34:17 +02:00
Irmen de Jong eff1a9fc53 m68k: loop dbra optimization 2026-08-23 14:58:44 +02:00
Irmen de Jong 5857382242 add %bssaddress and %slabsaddress directives to directly control the addresses of those segments 2026-08-23 14:16:06 +02:00
Irmen de Jong dfda43e747 more 6502 optimizations 2026-08-23 10:18:05 +02:00
Irmen de Jong 1029929111 6502 optimizations 2026-08-23 04:15:47 +02:00
Irmen de Jong ae55109a18 m68k: peepholes 2026-08-23 03:24:20 +02:00
Irmen de Jong 2ce7a63c1e IR: resolve loadx index-width type conflicts in usedRegisters; add conversion-collapse peephole 2026-08-23 02:41:12 +02:00
Irmen de Jong da6934ea33 m68k: prefer BSR/BRA over JSR/JMP 2026-08-23 01:09:15 +02:00
Irmen de Jong 2c8ccb706e added sfxgen library 2026-08-23 00:39:55 +02:00
Irmen de Jong 0d0d707976 Merge branch 'master' into new-codegens
# Conflicts:
#	simpleAst/src/prog8/code/ast/AstExpressions.kt
2026-08-23 00:25:27 +02:00
Irmen de Jong 4912c96f78 various codegen bug fixes 2026-08-23 00:12:12 +02:00
Irmen de Jong 79dd8d6b92 vm: rnd() is now identical to the xorshift algorithm of the other compilation targets
m68k: implemented clamp IR syscalls
new6502: implement EXTL
2026-08-22 22:29:41 +02:00
Irmen de Jong f750caa833 68k: fix vasm parse issue with .b, .l etc symbols -> now name-mangled 2026-08-22 21:46:16 +02:00
Irmen de Jong d17c978892 Merge branch 'master' into new-codegens
# Conflicts:
#	compiler/test/TestCompilerOnExamples.kt
#	examples/c64/cube3d.p8
#	examples/cube3d-gfx.p8
#	examples/cx16/cobramk3-gfx.p8
#	examples/cx16/cube3d.p8
2026-08-22 19:12:06 +02:00
Irmen de Jong 45cb50ebb1 fix crash on typecast of array element to long as operand of shift/add 2026-08-22 19:05:14 +02:00
Irmen de Jong 2cd2bb0121 optimized the 3d rotation examples 2026-08-22 19:03:14 +02:00
Irmen de Jong 5b890ac5d5 amiga: more strongly typed pointers in the library structs and routines 2026-08-22 18:23:01 +02:00
Irmen de Jong 894a3e90db codegen: fix pointer-typed struct field reads on 32-bit targets to use peekl and long address arithmetic 2026-08-22 12:40:34 +02:00
Irmen de Jong 234468690e resolve aliased/qualified struct types via StructTypeResolver with proper errors, infer for-loop node type in the list declaration's scope so imported module lists work, and fix 6502 codegen crash comparing against &struct.field 2026-08-21 23:00:00 +02:00
Irmen de Jong 5f50b254a2 rename list -> lists 2026-08-21 21:22:17 +02:00
Irmen de Jong c5b17ae880 finalize amiga audio routines 2026-08-21 21:08:28 +02:00
Irmen de Jong 4b320013cf Merge branch 'master' into new-codegens
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt
#	compiler/test/TestPointers.kt
#	docs/source/todo.rst
#	examples/test.p8
2026-08-21 19:14:29 +02:00
Irmen de Jong f3de26bf6c for-loop support for list/node iteration, and reverse str/array iteration using step -1 2026-08-21 07:28:39 +02:00
Irmen de Jong 65a1300147 cpu6502: implement pointer inplace invert/negate (byte/word/long/float) 2026-08-21 01:33:43 +02:00
Irmen de Jong ba6179a4d3 fix crash and errors on parenthesized/casted struct pointer field access (e.g. (t.next).flag, (t.port as ^^Thing).flag) 2026-08-21 00:48:19 +02:00
Irmen de Jong 6b91a464c0 fix spelling errors 2026-08-20 23:55:26 +02:00
Irmen de Jong 08ac5eff3e amiga: forward sys.memcopy to exec.CopyMem 2026-08-20 23:46:34 +02:00
Irmen de Jong 7de39bbd62 amiga: fix audio.wait_all() 2026-08-20 23:28:19 +02:00
Irmen de Jong 198cf4d722 IR: fix for loop iteration overflow for 256 iters 2026-08-20 23:20:42 +02:00
Irmen de Jong b0b8513cb8 amiga.audio using static allocations 2026-08-20 23:19:08 +02:00
Irmen de Jong 82cc66c0cc fix amiga audio (needs BeginIO) 2026-08-20 20:15:33 +02:00
Irmen de Jong c81d9b25ac Merge branch 'master' into new-codegens
# Conflicts:
#	compiler/test/ast/TestConst.kt
#	docs/source/todo.rst
2026-08-18 23:52:02 +02:00
Irmen de Jong 3aab457a47 fix const evaluation type truncation error for bitwise operations between constants of different types 2026-08-18 23:48:06 +02:00
Irmen de Jong c89f870dd0 amiga: adding audio.p8 for audio.device support
but test program still doesn't produce any audible sound...
2026-08-18 23:42:17 +02:00
Irmen de Jong ffb0a4d603 amiga: custom now includes interrupt vector offset symbols, cpuVBR and isPAL (set in grab_system) 2026-08-18 22:07:13 +02:00
Irmen de Jong df659d285b optimize imports 2026-08-18 01:28:35 +02:00
Irmen de Jong 2c2664c91f Merge branch 'vm32bit' into new-codegens 2026-08-18 00:12:59 +02:00
Irmen de Jong e0147a4a7e fix hardcoded uword pointer-size assumptions for 32-bit targets 2026-08-18 00:09:10 +02:00
Irmen de Jong 859223c98b fix hardcoded uword pointer-size assumptions for 32-bit targets 2026-08-17 23:57:37 +02:00
Irmen de Jong 7bdb9bc853 Merge branch 'new-codegens' into vm32bit 2026-08-17 22:19:46 +02:00
Irmen de Jong 4e87aa6b85 m68k: preserve immediate loads for named call arguments 2026-08-17 22:19:03 +02:00
Irmen de Jong 02667e9916 fix str/long type comparison 2026-08-17 20:36:39 +02:00
Irmen de Jong 9d57cbe263 m68k: fix float sgn instruction format 2026-08-17 20:06:52 +02:00
Irmen de Jong 8b25ff2390 Merge branch 'master' into vm32bit 2026-08-17 04:08:23 +02:00
Irmen de Jong 2cb753caf8 fix floating point issue for IR syscalls and m68k codegen 2026-08-17 04:03:09 +02:00
Irmen de Jong 34f0bea4e8 fix pointer unit tests, consolidate pointer type selection for target 2026-08-17 03:30:17 +02:00
Irmen de Jong 82828606e6 removed some string routines from shared because virtual target versions now need a 32 bit compatible implementation 2026-08-17 01:39:31 +02:00
Irmen de Jong ae751c4af4 Merge branch 'new-codegens' into vm32bit 2026-08-16 23:41:35 +02:00
Irmen de Jong 51b98204e9 fix unit test build issue 2026-08-16 23:39:55 +02:00
Irmen de Jong 12f375e070 Merge branch 'new-codegens' into vm32bit
# Conflicts:
#	examples/test.p8
2026-08-16 23:28:39 +02:00
Irmen de Jong 54a1a0704a allow strings to be used to initialize arrays in struct instances 2026-08-16 23:25:41 +02:00
Irmen de Jong 08b4168311 IR: preserve floating point value precision in struct fields 2026-08-16 21:30:25 +02:00
Irmen de Jong 4fdd12929a vm: switched to m68k like 32 bits registers 2026-08-16 21:14:15 +02:00
Irmen de Jong cc4f27d121 vm: making VM 32 bit
work in progress: see vm32.md there are failing tests still
2026-08-16 19:49:28 +02:00
Irmen de Jong 61041f4502 m68k: now calls IR register type verifier properly 2026-08-16 18:07:42 +02:00
Irmen de Jong 673ed91ee3 pointer and float size moved to memsizer, fix memory size of pass-by-ref to properly be size of pointer 2026-08-16 16:13:42 +02:00
Irmen de Jong 65b6280618 fix string compares on 32 bits targets 2026-08-16 15:55:30 +02:00
Irmen de Jong e50e175522 amiga: fix negative LVO offsets in library calls in p8ir file 2026-08-16 14:34:35 +02:00
Irmen de Jong fa1ceab78f M68K codegen: extend immediate call-argument forwarding and dead-store removal to floating-point registers and `p8_fregfile`. 2026-08-16 08:08:02 +02:00
Irmen de Jong 7eab0278b3 Merge branch 'master' into new-codegens 2026-08-16 07:32:34 +02:00
Irmen de Jong 36b12e9897 fix uword double increment/decrement bug 2026-08-16 07:26:21 +02:00
Irmen de Jong 69eaaf29c4 amiga: added sys.arguments for any CLI argument string 2026-08-16 07:08:22 +02:00
Irmen de Jong 69def73f3f m68k: allow assigning string constant to long/pointer variable (to get its address) 2026-08-16 06:18:33 +02:00
Irmen de Jong 9c2d8541c3 cx16: fix invalid code in sys.restore_irq(). Fixes #233 2026-08-16 03:33:40 +02:00
Irmen de Jong 6c93254758 m68k assembly peephole optimizer 2026-08-16 01:58:17 +02:00
Irmen de Jong a68cdd13a6 IR: added EXTL and EXTLS opcodes to help with pointer math 2026-08-15 22:58:15 +02:00
Irmen de Jong 295dfada61 amiga: add custom.filter_on() and filter_off() 2026-08-15 22:56:42 +02:00
Irmen de Jong d978be2f9d m68k: remove needless 0-extensions 2026-08-15 19:48:30 +02:00
Irmen de Jong 0a4601721a docs 2026-08-15 19:22:37 +02:00
Irmen de Jong d870c39aa4 m68k: use moveq and movea for call argument loading, drop +0 offset
- use moveq for small immediate call args (bytes 128-255 map to signed -128..-1)
- use movea.l for pointer/long args into address registers, dropping +0 suffix
- loadPointerToA0 also emits movea.l without the redundant +0 offset
- update stale C64 multiplexer expected output size
2026-08-15 18:49:28 +02:00
Irmen de Jong 2b93efbb33 m68k: remove redundant 0-extension, optimize and fix MSB/LSB extraction 2026-08-15 18:21:16 +02:00
Irmen de Jong c563562a6f m68k: optimized indirect JSR/JMP on 68020+ 2026-08-15 16:49:54 +02:00
Irmen de Jong 92a0d3cb1b m68k: optimize MUL and DIV instructions 2026-08-15 16:39:01 +02:00
Irmen de Jong 21d45a0a90 m68k: optimize CMP instruction, fix % 2026-08-15 16:00:26 +02:00
Irmen de Jong c87afc03ef m68k: optimize function call arg loading for immediates 2026-08-15 15:31:43 +02:00
Irmen de Jong 8dc9c4d3c2 optimize agent skills 2026-08-14 22:33:20 +02:00
Irmen de Jong e264aff607 added amiga3d example, fixing amiga blitter routines 2026-08-14 19:39:58 +02:00
Irmen de Jong 9591aad814 improving the C64 sprite multiplexer 2026-08-14 02:54:09 +02:00
Irmen de Jong 3208ef1f1a fix custom.set_aga_color 2026-08-14 00:33:50 +02:00
Irmen de Jong 36fcfa6965 implemented unary operations through pointers 2026-08-13 23:45:24 +02:00
Irmen de Jong ba7bf3f4fe cache IRProgram.registersUsed() 2026-08-13 23:25:00 +02:00
Irmen de Jong fcc93b385d IR: implement array in place / and % 2026-08-13 23:13:53 +02:00
Irmen de Jong 4c6d9b8f5f even more IR peephole optimizations 2026-08-13 23:01:41 +02:00
Irmen de Jong 6a3ee52bd5 more IR peephole optimizations 2026-08-13 22:50:50 +02:00
Irmen de Jong 8dec449a1c get rid of Either 2026-08-13 22:40:51 +02:00
Irmen de Jong a09942254d migrate CLI argument parsing from the obsolete kotlinx-cli library to Clikt 2026-08-13 22:33:16 +02:00
Irmen de Jong d702ace09f add support for pointer sizes >2 to configurable targets 2026-08-13 22:23:47 +02:00
Irmen de Jong f011d3f100 new language feature: %option private_symbols, and public keyword - to manage visibility 2026-08-13 22:05:58 +02:00
Irmen de Jong e404530ba4 Merge branch 'master' into new-codegens
# Conflicts:
#	compiler/src/prog8/compiler/Compiler.kt
#	compiler/src/prog8/compiler/CompilerDaemon.kt
#	gradle.properties
2026-08-13 18:32:47 +02:00
Irmen de Jong 48da22866c simplify and fix daemon output messages once more 2026-08-12 23:48:28 +02:00
Irmen de Jong 2e529f8849 amiga: added copper and blitter modules and amigablitcop example 2026-08-12 22:59:42 +02:00
Irmen de Jong 79a5ddcdd8 amiga: fix custom chip register names and offsets 2026-08-12 02:33:24 +02:00
Irmen de Jong 7aecb8d6eb amiga: add floating point support (assumes 68020+ and a FPU to run) 2026-08-11 23:12:49 +02:00
Irmen de Jong 73768e1876 new syntax feature done 2026-08-11 21:45:56 +02:00
Irmen de Jong 3514bd61ab new syntax feature: const type can be omitted (defaults to long for integers now) 2026-08-11 21:27:07 +02:00
Irmen de Jong a7f60a571d m68k: fix byte/word index value type mismatch 2026-08-11 20:58:13 +02:00
Irmen de Jong 3705064157 m68k: moveq improvements 2026-08-11 06:25:25 +02:00
Irmen de Jong 208205faea step 4 to allow for loop 'step' to be variable not only const: various correctness 2026-08-11 06:17:34 +02:00
Irmen de Jong 5af6afef61 step 3 to allow for loop 'step' to be variable not only const: 6502 codegen 2026-08-11 05:29:55 +02:00
Irmen de Jong a5a535806b step 2 to allow for loop 'step' to be variable not only const: step type casting done earlier 2026-08-11 04:20:00 +02:00
Irmen de Jong d582c1edf6 step 1 to allow for loop 'step' to be variable not only const 2026-08-11 04:07:56 +02:00
Irmen de Jong 7275137782 update some amiga stuff 2026-08-11 00:15:13 +02:00
Irmen de Jong f11da0488a fix false recursion message 2026-08-10 21:49:41 +02:00
Irmen de Jong ed1abe7e6b allow (optional) explicit for loop variable type to override the inferred type 2026-08-10 21:20:37 +02:00
Irmen de Jong 4dedc27e5b release 12.3.1 2026-08-10 20:54:26 +02:00
Irmen de Jong e70a1ed403 fix STOREZX.BYTE register type determination on m68k targets
STOREZX uses reg1 as the index register (not the value register),
so it should return indexRegType (WORD on 32-bit targets) regardless
of the instruction type. The code handled this for FLOAT/WORD/LONG
but missed the BYTE type case, causing register type conflicts
when indexing arrays with uword variables on m68k targets.
2026-08-09 23:18:46 +02:00
Irmen de Jong c53e72c5b2 Merge branch 'master' into new-codegens
# Conflicts:
#	compiler/test/TestCompilerOnExamples.kt
2026-08-09 23:05:04 +02:00
Irmen de Jong 5ba494c5e5 fix IR codegen needing explicit CMP instructions because of changed status bits contract (see commit #19238cc36) 2026-08-09 22:36:39 +02:00
Irmen de Jong b353904d30 fix for loop index register type issue in IR on m68k targets 2026-08-09 21:57:22 +02:00
Irmen de Jong a58a643d0d fixing unit test 2026-08-09 19:10:35 +02:00
Irmen de Jong 823ecfac6f add amiga ptplayer module (protracker module player) 2026-08-09 18:28:11 +02:00
Irmen de Jong 22035077af clean up for loop vardecls in examples 2026-08-09 17:05:34 +02:00
Irmen de Jong da8c3eda92 new language feature: implicit for loop variable declaration 2026-08-09 14:49:19 +02:00
Irmen de Jong 41c2b9c039 fix bit shift code for shifts by 16/32 bits or more 2026-08-09 14:26:45 +02:00
Recoordinate 5c16ebe377 Fix typo in comment (#232) 2026-08-09 12:35:32 +02:00
Irmen de Jong c7e25e323a some script fixes and izx0 image format 2026-08-09 12:26:59 +02:00
Irmen de Jong 1e2fc4d6f9 fix unit test on github hopefully 2026-08-09 00:48:43 +02:00
Irmen de Jong b5dfc0cd97 fix debug message and tests 2026-08-09 00:32:58 +02:00
Irmen de Jong 799486eb1b Merge branch 'master' into new-codegens 2026-08-09 00:23:23 +02:00
Irmen de Jong ed67a259de m68k: added compression module with ZX0 decompressor 2026-08-09 00:22:58 +02:00
Irmen de Jong d276fb9ed5 fix the overdraw in filled disc drawing routines 2026-08-09 00:20:22 +02:00
Irmen de Jong e0329e4457 added amiga iff load example 2026-08-08 22:13:53 +02:00
Irmen de Jong a3161fa117 implemented endian-aware msb/lsb handling 2026-08-08 19:34:12 +02:00
Irmen de Jong 9c8e81b566 IR: fix m68k indexing datatype size 2026-08-08 18:14:30 +02:00
Irmen de Jong e2e72a1791 Merge branch 'master' into new-codegens
# Conflicts:
#	compiler/src/prog8/CompilerMain.kt
2026-08-08 16:45:49 +02:00
Irmen de Jong ec9567a669 fix error output text in daemon mode 2026-08-08 16:44:43 +02:00
Irmen de Jong b3b983c7ec fix untyped pointer indexing on 32 bits targets (long vs uword) 2026-08-08 16:01:28 +02:00
Irmen de Jong 14ae579f00 amiga: add iffparse library module 2026-08-08 14:57:07 +02:00
Irmen de Jong 45f48d2e88 amiga: added %option amiga_chipram in blocks to allocate them in CHIP ram (for sound and graphics data) 2026-08-08 02:01:04 +02:00
Irmen de Jong 8d6ed7712d fix ir block cleanup bug 2026-08-08 01:24:09 +02:00
Irmen de Jong eea185bb83 keep label at top of block instead of moving vardecls before it, IR: fix bug that removed labels in blocks without code 2026-08-08 01:06:42 +02:00
Irmen de Jong 99d28ae557 fix typecast error for certain long/word address-of 2026-08-08 00:47:43 +02:00
Irmen de Jong 528762d961 add amiga screen example 2026-08-07 23:49:12 +02:00
Irmen de Jong c6e9a45293 fix m68k floating point code generation (68020 + FPU required) 2026-08-07 22:40:44 +02:00
Irmen de Jong 6880bd2a05 prog8 coder skill 2026-08-07 21:43:08 +02:00
Irmen de Jong 7547fb7747 array and string size limit raised to 32K on m68k targets 2026-08-07 20:47:06 +02:00
Irmen de Jong ff0e922674 dumpsymbols no longer includes compiler daemon info messages 2026-08-07 19:33:05 +02:00
Irmen de Jong 71f66171c4 amiga: fix const types in generated library files 2026-08-07 19:32:33 +02:00
Irmen de Jong c1f76cf073 m68k: prog8 doesn't have to do BSS clearing itself, the exe loaders already do this for us automatically 2026-08-07 19:07:40 +02:00
Irmen de Jong c2d151920d new docs about register allocation 2026-08-07 17:43:46 +02:00
Irmen de Jong c916570abd m68k now has long mul and div on 68000, uses utility.library on amiga if available 2026-08-07 12:32:47 +02:00
Irmen de Jong bdab975d14 fix amiga library pointer params 2026-08-07 11:28:29 +02:00
Irmen de Jong 1693e4ff9c Merge branch 'refs/heads/master' into new-codegens
# Conflicts:
#	examples/test.p8
2026-08-06 23:34:07 +02:00
Irmen de Jong 70d81298c1 fix multi-value initializer error when in nested scope 2026-08-06 23:32:08 +02:00
Irmen de Jong 6bf304b67b amiga500: add arexx library, timer.device manually opened 2026-08-06 23:17:28 +02:00
Irmen de Jong a5978a913b m68k: added coroutines module 2026-08-06 22:08:59 +02:00
Irmen de Jong e528f0ccaa amiga500: update generated library modules 2026-08-06 20:35:55 +02:00
Irmen de Jong e5f488dcfb new6502: fix align and 0-assign into array 2026-08-06 20:13:42 +02:00
Irmen de Jong 1de1c10b41 IR: fix register type error when 0-assigning into an array 2026-08-06 19:44:52 +02:00
Irmen de Jong c47a6bde0e IR: fix register type error when 0-assigning into an array 2026-08-06 19:31:05 +02:00
Irmen de Jong b8d38eccca new6502: fix long comparisons and STZ on 6502 2026-08-06 19:08:09 +02:00
Irmen de Jong 4c2144eb0d m68k: optimized bit shifts 2026-08-05 19:08:30 +02:00
Irmen de Jong 68399e8370 m68k: optimized bit manipulations 2026-08-05 13:50:12 +02:00
Irmen de Jong 7b61b066e6 m68k: optimized branches 2026-08-05 13:31:07 +02:00
Irmen de Jong bd2a47fb54 m68k: added agent skill 2026-08-05 13:20:13 +02:00
Irmen de Jong 2521df21e7 m68k: optimize bitwise not and zero loads 2026-08-05 12:50:34 +02:00
Irmen de Jong ddb6b597d6 m68k: optimize bitwise ops 2026-08-05 12:38:19 +02:00
Irmen de Jong c24fc9e81d emit floats as dc.s 2026-08-05 12:12:49 +02:00
Irmen de Jong 68f0d2285f Merge branch 'master' into new-codegens
# Conflicts:
#	docs/source/_static/symboldumps/skeletons-c128.txt
#	docs/source/_static/symboldumps/skeletons-c64.txt
#	docs/source/_static/symboldumps/skeletons-cx16.txt
#	docs/source/_static/symboldumps/skeletons-pet32.txt
#	docs/source/_static/symboldumps/skeletons-virtual.txt
#	docs/source/compiling.rst
#	gradle.properties
2026-08-05 11:05:02 +02:00
Irmen de Jong 25f883bb85 fix dangling bullet in history list 2026-08-05 11:03:02 +02:00
Irmen de Jong adc6d6626a attempt to fix Windows library file import bug (InvalidPathException) 2026-08-05 10:34:52 +02:00
Irmen de Jong 90ff904b6e add doc about newcodegen 2026-08-05 03:45:14 +02:00
Irmen de Jong ba54601eab release 12.3 2026-08-05 03:25:32 +02:00
Irmen de Jong 9937990618 Merge branch 'master' into new-codegens
# Conflicts:
#	examples/test.p8
2026-08-05 03:20:14 +02:00
Irmen de Jong 1c71f702f4 fix invalid const-folding of certain strings char classification routines such as isupper() 2026-08-05 02:55:36 +02:00
Irmen de Jong 3d3c567ea6 fix memory-byte read sign extension error when the byte was signed 2026-08-05 02:54:59 +02:00
Irmen de Jong 539c75bbf6 fix expression operand swap bug when there are side effects 2026-08-05 02:51:37 +02:00
Irmen de Jong 2683b6388a update history 2026-08-05 02:40:30 +02:00
Irmen de Jong e007d127f0 IR: smarter handling of x<<16 and x>>16 if x is a long 2026-08-05 02:01:21 +02:00
Irmen de Jong e5d9609db1 IR: added new multi-bit shifting opcodes 2026-08-05 01:42:49 +02:00
Irmen de Jong 051f3809cd IR: improved add/sub of small const values 2026-08-05 00:10:09 +02:00
Irmen de Jong b5fd07bb91 IR: added STOREIM instruction for immediate store constant to memory 2026-08-04 23:11:07 +02:00
Irmen de Jong 4097d51514 IR: more efficient code for comparisons against constants 2026-08-04 22:28:59 +02:00
Irmen de Jong 577815a1f4 m68k: cmp -> tst optimization 2026-08-04 22:06:20 +02:00
Irmen de Jong c2a942dd45 fix jump table on m68k 2026-08-04 21:43:39 +02:00
Irmen de Jong 82342268da fix too conservative Inliner gates, and possible endinanness problem. Optimized nested msb/lsb on m68k. 2026-08-04 21:01:29 +02:00
Irmen de Jong 30e576e253 defensive fix for wrong datatype in pointer comparisons 2026-08-04 20:44:05 +02:00
Irmen de Jong a5782c300a fix compiler crash on out of range pointer address values 2026-08-04 20:24:50 +02:00
Irmen de Jong 84f720b63b fix pokew/pokel pointer size mismatch 2026-08-04 19:46:49 +02:00
Irmen de Jong b8cded4373 revert back to fast (but slightly inaccurate) signed bitshifting for division optimization 2026-08-04 19:41:14 +02:00
Irmen de Jong 368dce93d9 new6502 codegen: implement long multiplications and divisions 2026-08-04 19:09:41 +02:00
Irmen de Jong 644226f925 update prog8 coder skill about multiple statements per line 2026-08-04 18:52:58 +02:00
Irmen de Jong 42e8e91034 disable signed div bias-shift method because of code bloat 2026-08-04 18:38:35 +02:00
Irmen de Jong af80ec6502 fix invalid optimization of signed division into a shift (wrong rounding result) - IR codegen 2026-08-04 17:42:12 +02:00
Irmen de Jong cf714d8e0c fix invalid optimization of signed division into a shift (wrong rounding result) - 6502 codegen only 2026-08-04 16:16:38 +02:00
Irmen de Jong 86c2f711ed fix invalid const-folding of certain strings char classification routines such as isupper() 2026-08-04 15:59:08 +02:00
Irmen de Jong 86c01e9d7b remaining m68k optimization issues noted in doc 2026-08-04 15:45:29 +02:00
Irmen de Jong 7138ee024b fix memory-byte read sign extension error when the byte was signed 2026-08-04 15:45:14 +02:00
Irmen de Jong b9f65186cb fix expression operand swap bug when there are side effects 2026-08-04 14:49:01 +02:00
Irmen de Jong 27aeafef0d more ast optimization gatings for m68k 2026-08-04 13:58:03 +02:00
Irmen de Jong 16901212d4 m68k: fix divmod() and lmh() call convention 2026-08-04 13:40:30 +02:00
Irmen de Jong 49f5c30e8c 6502: better error message for missing signed divmod support 2026-08-04 13:09:06 +02:00
Irmen de Jong bef1678c2a IR: divmod no longer uses stack to return the quotient and remainer 2026-08-04 12:46:36 +02:00
Irmen de Jong 3459ffcdb9 m68k: fix wrong divm/mulm code gen 2026-08-04 05:19:53 +02:00
Irmen de Jong 4cfe9b2729 Merge branch 'master' into new-codegens 2026-08-04 04:58:22 +02:00
Irmen de Jong a919ea63e4 remove wrong ast node copy() overrides 2026-08-04 04:54:53 +02:00
Irmen de Jong 97e5a6f7bb amiga500 textio: use dos VPrintf instead of separate conv calls 2026-08-04 04:50:01 +02:00
Irmen de Jong 79c05ae396 m68k: fix divs sign extension bug 2026-08-04 03:05:13 +02:00
Irmen de Jong f25ea95bdf m68k: fix extb.l on 68000, checks for long division/multiplication on 68000 2026-08-04 01:51:14 +02:00
Irmen de Jong 45bf8ba72a m68k: fix long and float variable dc.l/dc.s, fix 68020 long division instruction syntax 2026-08-04 01:00:27 +02:00
Irmen de Jong 07910fc543 IR: fix register type error for power of 2 multiplies as shifts 2026-08-04 00:06:09 +02:00
Irmen de Jong c64fdcffa8 IR: fix register type error for power of 2 multiplies as shifts 2026-08-04 00:05:13 +02:00
Irmen de Jong c0b8cf827d fix m68k eor instruction syntax 2026-08-04 00:04:19 +02:00
Irmen de Jong fc38847af4 fix addq/subq value range to 1-8 2026-08-03 23:33:30 +02:00
Irmen de Jong fea52c6145 uword comparison optimizations not for m68k 2026-08-03 22:45:46 +02:00
Irmen de Jong 4b20440efe another bit shift optimization not for m68k 2026-08-03 22:30:30 +02:00
Irmen de Jong c39410afc2 x/256 ast optimization not for m68k 2026-08-03 22:19:29 +02:00
Irmen de Jong 93b953cf60 implement IR ADDIM and SUBIM in the new codegens 2026-08-03 21:53:54 +02:00
Irmen de Jong 01c82f92eb Merge branch 'refs/heads/master' into new-codegens
# Conflicts:
#	examples/test.p8
#	intermediate/src/prog8/intermediate/IRInstructions.kt
2026-08-03 19:36:25 +02:00
Irmen de Jong 13e032e1a3 add new IR instructions ADDIM and SUBIM 2026-08-03 19:31:57 +02:00
Irmen de Jong 8405e6f2a3 fix endianness issue with msw/lsw on m68k 2026-08-03 15:49:39 +02:00
Irmen de Jong 491b28f6b6 chained expression splitting is only beneficial on the 6502 2026-08-03 15:26:52 +02:00
Irmen de Jong 9286d6b2c8 fix amiga sysinit 2026-08-03 15:21:33 +02:00
Irmen de Jong d144235dc9 Merge branch 'master' into new-codegens 2026-08-03 14:35:47 +02:00
Irmen de Jong c3b5ee3dfd optimize imports 2026-08-03 14:31:42 +02:00
Irmen de Jong b0e0058310 split huge test file 2026-08-03 14:23:21 +02:00
Irmen de Jong 9cbbf85a85 fix chained expression optimization bug 2026-08-03 14:22:46 +02:00
Irmen de Jong 2fa82ff3ad fix ast remainder optimization check 2026-08-03 13:26:11 +02:00
Irmen de Jong 9936a855ab fix ast remainder optimization check 2026-08-03 13:05:26 +02:00
Irmen de Jong 3e03980478 tweaking ast optimizations for m68k 2026-08-03 12:45:48 +02:00
Irmen de Jong e042756453 add timer.device 2026-08-03 08:09:11 +02:00
Irmen de Jong 84d5247b78 fix m68k enum field symbols 2026-08-03 05:30:09 +02:00
Irmen de Jong 4682462c19 added utility.library 2026-08-03 04:16:10 +02:00
Irmen de Jong b6e503a67b fix amiga textio and m68k alignment issues 2026-08-03 02:33:19 +02:00
Irmen de Jong dedae29d72 Merge branch 'master' into new-codegens 2026-08-02 17:42:33 +02:00
Irmen de Jong bb1d1e2007 gradle --non-interactive didn't work 2026-08-02 17:27:39 +02:00
Irmen de Jong 0aa6c130e8 update to kotlin 2.4.10 2026-08-02 17:23:22 +02:00
Irmen de Jong 092116de1d custom grab_system() and return_system() added for demo/game hardware banging programs
added amigarasters example
2026-07-16 23:04:14 +02:00
Irmen de Jong bf0733f8b3 amiga LVOs (extsub library callfars) are now represented by their familiar negative decimal offset number instead of a hex unsigned long.
Actual JSR to the library LVO uses the symbolic name if possible to create code that is easier to debug
2026-07-16 21:42:04 +02:00
Irmen de Jong b0cf649c2d register packer does't work well 2026-07-16 20:33:27 +02:00
Irmen de Jong 3499121f9c better amiga program startup 2026-07-16 00:35:44 +02:00
Irmen de Jong c43cacdc2f added a IR RegisterPacking optimization step that reduces the number of registers through reuse of register slots 2026-07-15 22:39:19 +02:00
Irmen de Jong ead1e5e879 make exit() work on m68k 2026-07-15 00:18:40 +02:00
Irmen de Jong 4c1f193d53 amiga custom chip registers 2026-07-14 22:20:12 +02:00
Irmen de Jong abaaea33d8 more intuition constants 2026-07-14 20:13:03 +02:00
Irmen de Jong 51257e47da structs in symboldumps 2026-07-14 08:05:03 +02:00
Irmen de Jong fe42040d48 structs in symboldumps 2026-07-14 08:04:32 +02:00
Irmen de Jong 67ddd97f3d fix skill to allow fixed-size arrays as struct fields 2026-07-14 07:31:10 +02:00
Irmen de Jong abea7bf329 fix const evaluation type problem in bitwise operations 2026-07-14 07:28:31 +02:00
Irmen de Jong bc3f49dc13 unit test for bitwise long const errors 2026-07-14 07:27:44 +02:00
Irmen de Jong 7565d04cf5 fix missing constants in amiga lib dumps 2026-07-14 07:27:15 +02:00
Irmen de Jong 0d76eea1be unit test for bitwise long const errors 2026-07-14 07:24:01 +02:00
Irmen de Jong 9a2851a9d5 fix const evaluation type problem in bitwise operations 2026-07-14 07:12:52 +02:00
Irmen de Jong 5e1612f204 improved amiga libconvert script (now requires clang to parse the C header files) 2026-07-14 06:49:29 +02:00
Irmen de Jong 316181b971 fix skill to allow fixed-size arrays as struct fields 2026-07-14 06:26:08 +02:00
Irmen de Jong e753eed4d4 amiga lib dumps with structs 2026-07-14 04:52:45 +02:00
Irmen de Jong b52a07c7c4 tweak m68k roxl/roxr semantix (X bit)
work on m68k conv routines
2026-07-14 02:49:19 +02:00
Irmen de Jong f210c8b0db amiga library calls 2026-07-13 22:58:34 +02:00
Irmen de Jong f865a9801f output memory mapped variables on m68k asm 2026-07-12 19:18:17 +02:00
Irmen de Jong 6a165e951b amiga500 target 2026-07-12 19:05:13 +02:00
Irmen de Jong 9322fbb234 better syntax highlighting in docs
added m68k qemubootinfo example
2026-07-12 14:20:31 +02:00
Irmen de Jong 4b7e2dce9d better syntax highlighting in docs 2026-07-12 13:05:05 +02:00
Irmen de Jong 7bda2e987c remove redundant DONTCARE split pointer array conversions on non-6502 targets 2026-07-12 12:55:00 +02:00
Irmen de Jong e645b0abe1 add pokep() and peekp() builtin functions 2026-07-12 12:54:29 +02:00
Irmen de Jong f10a58ee1b fix m68k long/string arrays 2026-07-12 12:09:44 +02:00
Irmen de Jong f498f598bf fix m68k concat 2026-07-12 04:49:13 +02:00
Irmen de Jong 4dfcb59b6f fix daemon 2026-07-12 04:03:10 +02:00
Irmen de Jong 4506a3cb3e pass compilation target to more places to be able to determine correct types 2026-07-12 03:52:57 +02:00
Irmen de Jong 247559858f fix bitwise instructions on m68k 2026-07-11 23:40:46 +02:00
Irmen de Jong 8a8cf3abc7 Merge branch 'master' into new-codegens
# Conflicts:
#	compiler/test/TestCompilerOnExamples.kt
2026-07-11 23:13:04 +02:00
Irmen de Jong cf2a5ae4f9 fix test sizes 2026-07-11 23:06:26 +02:00
Irmen de Jong 0cf488b586 fix some array issues 2026-07-11 23:01:23 +02:00
Irmen de Jong a019f22f47 all arena allocators now return pointer type 2026-07-11 22:26:14 +02:00
Irmen de Jong 9587e1b724 Merge branch 'master' into new-codegens 2026-07-11 22:17:25 +02:00
Irmen de Jong e5865a1446 fix delete root node bug in binarytree.p8 2026-07-11 22:16:51 +02:00
Irmen de Jong 7adbdf39ac specific for 6502 2026-07-11 22:02:08 +02:00
Irmen de Jong 57714efb40 added pushp() and popp() builtin functions 2026-07-11 21:49:22 +02:00
Irmen de Jong dafb0906c3 fix some m68k pointer/uword stuff 2026-07-11 21:13:21 +02:00
Irmen de Jong 96bb64aebb fix some m68k fp and alignment stuff 2026-07-11 20:36:03 +02:00
Irmen de Jong 57254233e4 Merge branch 'master' into new-codegens
# Conflicts:
#	codeCore/src/prog8/code/target/NormalMemSizer.kt
2026-07-11 18:22:11 +02:00
Irmen de Jong 9ec4b35de9 fix m68k memclear 2026-07-11 18:20:41 +02:00
Irmen de Jong 9adbd77ce4 vasm package is borked 2026-07-11 17:28:15 +02:00
Irmen de Jong 8957e8fff7 needs devmode as well 2026-07-11 17:25:16 +02:00
Irmen de Jong 3aa0344c4a needs beta channel 2026-07-11 17:23:14 +02:00
Irmen de Jong 54de2c5fbf attempt to install vasm in github actions 2026-07-11 17:21:12 +02:00
Irmen de Jong 5b5f553d37 improved error message when attempting to alias types,
fix size test
2026-07-11 17:04:52 +02:00
Irmen de Jong 0e978b250b added 'pointer' as type alias for uword or long depending on the compilation target's pointer size 2026-07-11 16:59:24 +02:00
Irmen de Jong 67e6888009 fix ast check for m68k to also consider library files, align code on even addresses, start implementing syscalls 2026-07-11 14:59:46 +02:00
Irmen de Jong b9fe1f2057 fix fibonacci sequence to start from 0 2026-07-11 14:48:52 +02:00
Irmen de Jong debb91965b fix ast check for m68k to also consider library files, align code on even addresses 2026-07-11 13:43:14 +02:00
Irmen de Jong d0b3fefd57 fix peek/poke to take long addresses on m68k 2026-07-11 13:16:16 +02:00
Irmen de Jong 5cc3623a2a m68 adding input routine, fix linkerscript, qemu bootinfo 2026-07-11 12:41:35 +02:00
Irmen de Jong d08f532a10 m68k float support 2026-07-11 02:39:14 +02:00
Irmen de Jong 35b7e4a4de fix reference return types vs pointer 2026-07-11 00:07:36 +02:00
Irmen de Jong 38b77d8817 ir: fix usage of STOREZ on pointers 2026-07-10 23:49:48 +02:00
Irmen de Jong 3078f402bb fix uword literal assignment to pointer on m68k 2026-07-10 22:35:07 +02:00
Irmen de Jong f977daafbb value casting now gets passed the compilation target (needed for correct pointer type casting) 2026-07-10 22:23:33 +02:00
Irmen de Jong aabd99e9eb fix uword to pointer assignment problems 2026-07-10 21:32:59 +02:00
Irmen de Jong e7dbab1822 fix defer on m68k by making mask variable a word so correct memory LSR instruction can be used 2026-07-10 17:40:47 +02:00
Irmen de Jong cf6efd76f0 making sure memory() is long on m68k 2026-07-10 04:51:54 +02:00
Irmen de Jong 8a7922ffcc fix split words arrays on m68k to be not split 2026-07-10 01:47:18 +02:00
Irmen de Jong c7a8e84fd0 fix more pointer type handling on m68k 2026-07-09 03:44:41 +02:00
Irmen de Jong cb57bea862 fix inline asmsub handling, more optimized strings and sys routines 2026-07-09 03:25:30 +02:00
Irmen de Jong b24ce80945 tweaking m68k library 2026-07-09 00:13:20 +02:00
Irmen de Jong eb18397035 Merge branch 'master' into new-codegens 2026-07-08 23:37:48 +02:00
Irmen de Jong cfd1f951f0 should call through 2026-07-08 23:36:48 +02:00
Irmen de Jong b0e55eb25b fix another float to long cast error 2026-07-08 23:27:10 +02:00
Irmen de Jong 37a86f3899 switch back to M68020 CPU so code migth eventually run on a stock Amiga 1200 2026-07-08 23:03:10 +02:00
Irmen de Jong 5700ae446f improve m68k codegen, add integer sqrt 2026-07-08 22:28:00 +02:00
Irmen de Jong cb240f6a4b Merge branch 'master' into new-codegens 2026-07-08 21:02:17 +02:00
Irmen de Jong b405ffb8bd fix missing float to long cast, fix missing inlined floats routines prefix 2026-07-08 20:42:54 +02:00
Irmen de Jong 0fa08310e7 add missing floats.pow() to the docs, optimize the signature of many float functions 2026-07-08 19:28:34 +02:00
Irmen de Jong 968d9819db removed -nostdlib option 2026-07-07 23:31:20 +02:00
Irmen de Jong 54df69a2b3 allow array args for pointer params in m68k 2026-07-07 22:14:38 +02:00
Irmen de Jong 88b3291785 added ELF/RAW output type selection for m68k, fix div garbage register data 2026-07-07 21:49:45 +02:00
Irmen de Jong c9c68f7d8c clean up some pointer type stuff in m68k 2026-07-07 20:36:00 +02:00
Irmen de Jong 2d3f0baa51 add a few m68k math routines
properly output data section in m68k
2026-07-07 20:03:13 +02:00
Irmen de Jong bd4184252f add sys.MAX_LONG and sys.MIN_LONG for m68k 2026-07-07 05:02:47 +02:00
Irmen de Jong f043dba23d Merge branch 'master' into new-codegens 2026-07-07 04:39:09 +02:00
Irmen de Jong f85c59b12c add sys.MAX_LONG and sys.MIN_LONG 2026-07-07 04:37:59 +02:00
Irmen de Jong e6a267bd3f fix 32 bits address-of 2026-07-07 04:27:47 +02:00
Irmen de Jong a532fb411b IR POINTER size tweaks 2026-07-07 02:58:05 +02:00
Irmen de Jong 1f4ffdffc1 fix IR constant prefixing/no prefixing 2026-07-07 01:45:55 +02:00
Irmen de Jong 1053a9a761 Merge branch 'master' into new-codegens 2026-07-07 01:00:13 +02:00
Irmen de Jong 34eedd1fe7 fix missing return values in symboldumps 2026-07-07 00:41:14 +02:00
Andy De George 2c10e2b102 Fix the swap documentation now that it's a statement and no longer a function (#229)
* [docs] Remove swap from built in functions

* Move swap info to programming doc

* Reintroduce swap statement documentation

* Apply suggestion from @Thraka
2026-07-07 00:08:29 +02:00
Irmen de Jong 7f51bbd777 working on encoding support for variable sized pointers in the IR 2026-07-07 00:06:28 +02:00
Irmen de Jong 5c36e21948 working on fixing pointer arithmethic
but see transformWithPointerArithmetic() still unfinished
2026-07-06 02:20:32 +02:00
Irmen de Jong a46443417e disable zeropage usage on non-6502 targets 2026-07-05 20:02:14 +02:00
Irmen de Jong 57055b3b55 map m68k cpu registers to and from slots 2026-07-05 16:25:20 +02:00
Irmen de Jong 559521c6df m68k better BSS 2026-07-05 07:07:19 +02:00
Irmen de Jong 11e3b39486 m68k (incomplete) attempt to fix 32 bits pointers 2026-07-05 06:31:24 +02:00
Irmen de Jong 1c50e397de m68k registers 2026-07-05 03:41:59 +02:00
Irmen de Jong 05bea61a69 builtin functions now have a signature in the symbol dump files 2026-07-05 01:11:40 +02:00
Irmen de Jong 2a8b24074b remove error about memory mapped pointers 2026-07-05 00:53:13 +02:00
Irmen de Jong 99ead2c74c m68k 2026-07-04 23:18:03 +02:00
Irmen de Jong f557d2bee0 Merge branch 'master' into new-codegens 2026-07-04 18:28:14 +02:00
Irmen de Jong 1f184a14ae diskio: document path length limit 2026-07-04 18:24:19 +02:00
Irmen de Jong 8be1cd1090 pet32: added some alternative hopefully unused ZP locations (as long as you're not using tape I/O which is a common assumption for every other cbm target as well) 2026-07-04 18:12:31 +02:00
Irmen de Jong 5278f9a595 new codegen fix floatparse / mul 2026-07-04 18:00:49 +02:00
Irmen de Jong dd1a886b66 Merge branch 'master' into new-codegens 2026-07-04 00:04:50 +02:00
Irmen de Jong c3ec400ee7 pet32: don't use problematic zeropage locations 2026-07-04 00:03:22 +02:00
Irmen de Jong 7f95377ad7 newcodegen: fixing more comparisons and branches 2026-07-04 00:01:53 +02:00
Irmen de Jong 903c553f94 newcodegen: fixing more comparisons and branches 2026-07-03 22:15:39 +02:00
Irmen de Jong 0e7237c64a newcodegen: fixing branches 2026-07-03 21:13:27 +02:00
Irmen de Jong 122af69cdc newcodegen: fixing comparisons 2026-07-03 05:14:07 +02:00
Irmen de Jong 786d742e76 newcodegen: fixing floats 2026-07-03 02:04:43 +02:00
Irmen de Jong c4c46fd282 fix stack byte order in new codegen 2026-07-02 22:49:10 +02:00
Irmen de Jong 5a49c97e5d fix zeropage var inits in new codegen, better 65c02 instruction usage 2026-07-02 22:12:43 +02:00
Irmen de Jong 2cd195b343 fix string encoding in new codegen 2026-07-02 21:03:54 +02:00
Irmen de Jong 36238267d0 reuse AssemblyProgram6502 in newcodegen for 6502 2026-07-02 01:36:25 +02:00
Irmen de Jong 52fe2f2c0d adjust compilation options etc for the newCodegen flag 2026-07-02 01:21:18 +02:00
Irmen de Jong 4bf0eba2f8 Merge branch 'master' into new-codegens
# Conflicts:
#	.idea/compiler.xml
#	codeCore/src/prog8/code/core/CompilationOptions.kt
#	codeGenIntermediate/test/TestVmCodeGen.kt
#	codeGenNew6502/src/prog8/codegen/new6502/New6502CodeGenerator.kt
#	compiler/src/prog8/CompilerMain.kt
#	compiler/src/prog8/compiler/Compiler.kt
#	compiler/src/prog8/compiler/CompilerDaemon.kt
#	compiler/test/TestCompilerOnExamples.kt
#	compiler/test/helpers/compileXyz.kt
#	scripts/clean.sh
#	settings.gradle
2026-07-02 01:14:55 +02:00
Irmen de Jong 5130a74754 removed expericodegen 2026-07-02 01:07:21 +02:00
Irmen de Jong 9d1bcfc0c6 remove codegenExperimental module 2026-07-02 00:54:11 +02:00
Irmen de Jong 15756a8977 rename -expericodegen into -newcodegen that should switch all existing 6502 codegens to using the new one based on IR 2026-07-02 00:51:45 +02:00
Irmen de Jong 0ab36bcbf6 added qemu68k compilation target 2026-07-01 23:47:13 +02:00
Irmen de Jong 36eef51e1c new package structure for new codegens 2026-07-01 23:03:32 +02:00
Irmen de Jong 309c25a993 add new codegens that are based on the IR 2026-07-01 22:22:53 +02:00
Irmen de Jong e61db5ed3f cleanup 2026-07-01 22:16:00 +02:00
Irmen de Jong cc5ef21fd6 don't publish unfinished codegens yet 2026-07-01 21:40:00 +02:00
Irmen de Jong f1f925ecee clarify sqrt() behavior on values <0 2026-07-01 20:51:06 +02:00
Irmen de Jong ab422c0ebc fix wrong codegen and 'empty' library subroutines 2026-07-01 20:25:00 +02:00
Irmen de Jong 05a13463f4 tweak agent skills and helper routine visibilities 2026-07-01 17:55:13 +02:00
Irmen de Jong 19238cc366 IR: introduce strict and broad status flags contract for cpu targets
fix cmpi for words and longs
2026-07-01 01:38:35 +02:00
Irmen de Jong e3d4401d99 fix callfar 2026-07-01 00:02:26 +02:00
Irmen de Jong 5da4efc2a3 shorten long operations by using a Y-loop 2026-06-30 22:47:50 +02:00
Irmen de Jong c0c44156f1 new codegen: implement roxl/roxr longs, float casting, callfar 2026-06-30 22:18:41 +02:00
Irmen de Jong 8645833319 new codegen: float support and CALLI 2026-06-30 20:40:34 +02:00
Irmen de Jong 6a867d98eb new codegen: syscall support started 2026-06-30 05:57:19 +02:00
Irmen de Jong 58363ff4e4 Add required support for inlined asmsub routines in IR and new codegen 2026-06-30 03:17:19 +02:00
Irmen de Jong 2555aa72ca improve codegen argument slot processing (func call arg order) 2026-06-30 02:45:19 +02:00
Irmen de Jong 3b94fd4c4b IR: more consistent BSS handling 2026-06-30 01:14:31 +02:00
Irmen de Jong 95f500c32f fixing codegen 2026-06-29 23:10:55 +02:00
Irmen de Jong 870c2e8827 add name to StExtSubParameter to pass this to the codegen (required for @R0 etc) 2026-06-29 04:43:24 +02:00
Irmen de Jong af2360648f implement codegen mul and div routines, and fix symboloffsets 2026-06-29 04:43:24 +02:00
Irmen de Jong bb95d40d6d asm syntax 2026-06-29 04:43:24 +02:00
Irmen de Jong 55ea599fca Remove hardcoded external KERNAL entry points in codegen 2026-06-29 04:43:24 +02:00
Irmen de Jong fd5fdf3d13 IR codegen: Emit extsub as inline assembly equate for codegen references 2026-06-29 04:43:24 +02:00
Irmen de Jong ca129edce4 adding new 6502 codegen 2026-06-29 04:43:23 +02:00
Irmen de Jong c1694a4b08 add m68k codegen skeleton 2026-06-29 04:43:23 +02:00
Irmen de Jong a348bbbebe adding new 6502 codegen based on IR files 2026-06-29 04:43:23 +02:00
Irmen de Jong e6b0a89d64 asm coder skill 2026-06-29 04:43:09 +02:00
Irmen de Jong 9a4821d1e3 extra asm syntax rule in skill 2026-06-28 22:50:43 +02:00
Irmen de Jong 71583a5b93 Move symbol prefixing logic from 6502 codegen to IR generation. 2026-06-28 21:33:10 +02:00
Irmen de Jong 407ba34c72 IR fix certain setmsb/setlsb and rol/ror crashes on indexed pointers 2026-06-27 14:50:45 +02:00
Irmen de Jong fc4782a35b Support &blockname in IR generation 2026-06-27 12:54:36 +02:00
Irmen de Jong 7a83b7db1b Fix dark mode styling for ..data:: headers in documentation 2026-06-27 10:03:15 +02:00
Irmen de Jong a827f4c4ce Support block-level %align in IR 2026-06-27 09:51:08 +02:00
Irmen de Jong 01c5794799 IR: fixing zeropage array initialization 2026-06-27 02:42:55 +02:00
Irmen de Jong 1c093cca2d fix zeropage array initialization for split word arrays 2026-06-27 01:39:38 +02:00
Irmen de Jong a85b267872 streamline asm generation 2026-06-26 20:33:40 +02:00
Irmen de Jong 5e0c1c9794 Optimize parser by introducing SLL prediction mode with LL fallback for improved performance and error handling. 2026-06-25 03:53:18 +02:00
Irmen de Jong 887df44359 fix IR loadhr/storehr parsing from p8ir 2026-06-25 03:30:10 +02:00
Irmen de Jong a5fc162c71 Optimize block sorting and subroutine reordering logic in AST processing steps. 2026-06-25 02:22:44 +02:00
Irmen de Jong 9d3fc99d71 ignores 2026-06-25 01:58:14 +02:00
Irmen de Jong a871713907 some more explanations 2026-06-23 20:57:06 +02:00
Irmen de Jong 2a19e5682a add cx16 dynamicbanking example 2026-06-23 20:45:34 +02:00
Irmen de Jong 22d4fa73d6 rename bankmanager to bankselector 2026-06-23 19:07:31 +02:00
Irmen de Jong 2ed95c52ef tune JSRFAR bank byte setup 2026-06-23 01:01:35 +02:00
Irmen de Jong 73fb04aaf5 Add support for unique call-site IDs in banked subroutines 2026-06-23 00:28:22 +02:00
Irmen de Jong f97a7f426c improved cmp() documentation 2026-06-22 19:53:45 +02:00
Irmen de Jong c1189617f3 fix 64tass repo link 2026-06-22 00:12:55 +02:00
Irmen de Jong 78d43c3cb6 Merge branch 'refs/heads/bugfix12.2.1'
# Conflicts:
#	examples/test.p8
2026-06-20 19:50:42 +02:00
Irmen de Jong 86f9b6aa69 fixed cmp() sign/flags errors 2026-06-20 19:49:45 +02:00
Irmen de Jong 8df2bb8d2e Merge branch 'bugfix12.2.1' 2026-06-20 15:47:41 +02:00
Irmen de Jong 33f30d925e Update documentation with proper example links 2026-06-20 15:45:01 +02:00
Irmen de Jong c8c51245af Merge branch 'bugfix12.2.1' 2026-06-20 15:18:14 +02:00
Irmen de Jong db227679a8 fix empty/full boundary checks on various datastructions in the buffers module 2026-06-20 15:05:37 +02:00
Irmen de Jong 02110de844 optimized floats minf, maxf, clampf routines 2026-06-20 13:56:17 +02:00
Irmen de Jong 7d77a0eca6 Merge branch 'bugfix12.2.1'
# Conflicts:
#	compiler/test/TestCompilerOnExamples.kt
2026-06-19 23:37:49 +02:00
Irmen de Jong 0f3475128a fix divmod_uw_asm to not clobber R15 anymore 2026-06-19 23:23:53 +02:00
Irmen de Jong e6e4714b02 adjust pet music tempo a little
todo about memory mapping pointers
2026-06-19 19:59:54 +02:00
Irmen de Jong d83ece397d add configurable default launcher types for custom compilation targets 2026-06-19 18:58:05 +02:00
Irmen de Jong a3c768bc6b add floats.mod() 2026-06-19 18:35:09 +02:00
Irmen de Jong a3d14003cb Enhanced inlining: support subroutines with one simple parameter. NOTE: risky optimization, maybe still broken in obscure cases
also improve parent pointer relinking, fix namespace scoping, and refine assembly checks.
2026-06-19 04:57:54 +02:00
Irmen de Jong d451953e40 improve prog8 skill 2026-06-19 02:18:41 +02:00
Irmen de Jong d505c42d15 improve undefined symbol error handling
(cherry picked from commit 8c92663824)
2026-06-19 02:16:11 +02:00
Irmen de Jong 8c92663824 improve undefined symbol error handling 2026-06-19 02:13:03 +02:00
Irmen de Jong 10d86eb4d8 Merge remote-tracking branch 'origin/master' 2026-06-19 00:45:32 +02:00
markjreed ee5e734f23 feat: add symbols for enharmonic flats (#226) 2026-06-19 00:44:50 +02:00
Irmen de Jong 6b233b7da3 pet: add sys.set_irq() and sys.restore_irq() support routines 2026-06-19 00:34:53 +02:00
Irmen de Jong a140519374 fix pet music example to use higher octave to be able to play correct B section end note 2026-06-18 23:57:38 +02:00
Irmen de Jong 2b118f2e42 pet32: add petsnd REST constant for silence, and set_gap() for intra note silence gap 2026-06-18 23:13:13 +02:00
Irmen de Jong be9d9fe377 pet32: added new playback functions to petsnd module to play songs (blocking or non-blocking). New 'music' example added for pet. 2026-06-18 21:02:33 +02:00
Irmen de Jong d1345aafec don't limit callfar/banked call support on fixed set of built in targets
but you have to set supports_banked_calls = true in the custom target config file.
2026-06-18 19:09:10 +02:00
Irmen de Jong d5932a7a57 improved bankin support error message 2026-06-17 23:45:30 +02:00
Irmen de Jong 5751beb72f fix some more LSP tests 2026-06-17 20:52:59 +02:00
Irmen de Jong 02194a735a Add supports_banked_calls property to target configuration files 2026-06-17 20:30:21 +02:00
Irmen de Jong b5fc25b2f4 Merge branch 'v12.2.1'
# Conflicts:
#	compiler/test/TestCompilerOnExamples.kt
#	docs/source/_static/symboldumps/skeletons-cx16.txt
2026-06-17 19:41:34 +02:00
Irmen de Jong 5a593b9ef3 Preserve cx16.r15 register in % for words. Update related code and documentation accordingly. 2026-06-17 19:23:19 +02:00
Irmen de Jong b411914624 Document virtual register clobbering details and update related comments. 2026-06-17 03:00:33 +02:00
Irmen de Jong 0f80b695e7 Fix invalid optimizer edge cases 2026-06-17 01:18:39 +02:00
Irmen de Jong 63c425f247 Merge branch 'refs/heads/v12.2.1' 2026-06-16 23:25:18 +02:00
Irmen de Jong 276627abcb remove broken expression optimization that combined a=x, a*=y into a=x*y 2026-06-16 23:19:42 +02:00
Irmen de Jong 34225d10aa add missing division and modulus code generation support for LONG variables 2026-06-16 23:18:07 +02:00
Irmen de Jong 69261c51b3 IR: Add single-bit manipulation opcodes (BITTST, BITSET, BITCLR, BITTOG) 2026-06-16 21:52:31 +02:00
Irmen de Jong 63d7ef7105 IR: Replace 6502-specific hardware register LOAD/STORE opcodes with generic, parameterized LOADHR/STOREHR opcodes 2026-06-16 20:59:51 +02:00
Irmen de Jong 88a3e5b23f Merge branch 'v12.2.1'
# Conflicts:
#	compiler/test/TestCompilerOnExamples.kt
#	compiler/test/codegeneration/TestArrayThings.kt
#	examples/test.p8
2026-06-16 00:48:35 +02:00
Irmen de Jong 282c3aa00b fix a return split word array codegen issue where it didn't use lsb suffix 2026-06-16 00:39:51 +02:00
Irmen de Jong 19f3b03ac3 remove problematic inlining for subroutines having 1 or more parameters, to avoid an endless loop 2026-06-16 00:02:05 +02:00
Irmen de Jong 1b5efe1e5d fix datatype size comparison for strings vs pointers 2026-06-15 09:03:13 +02:00
Irmen de Jong 2b5a15a178 limit iteration cycles in AST processing steps to prevent endless loops 2026-06-15 03:35:14 +02:00
Irmen de Jong a9f22d9375 handle library path files earlier during import to mark them as library files 2026-06-14 19:29:44 +02:00
Irmen de Jong f9b3514f2a fixes and tests for embedded arrays in structs 2026-06-14 16:18:23 +02:00
Irmen de Jong be12fb507e arrays in structs 2026-06-14 15:35:30 +02:00
Irmen de Jong b2dcd02ee4 prioritize frequently accessed variables for zeropage allocation based on usage scores 2026-06-14 13:41:31 +02:00
Irmen de Jong 5c23471254 Merge branch 'v12.2.1' 2026-06-13 12:16:14 +02:00
Irmen de Jong 6cb6f37871 exclude private symbols from completion suggestions in the LSP 2026-06-13 12:15:11 +02:00
Irmen de Jong 67e284b680 complexity based argument evaluation order in sub calls to optimize register spill handling 2026-06-13 03:22:15 +02:00
Irmen de Jong 1e5ba48fe0 tweak banked call support checks on compilation targets 2026-06-13 02:01:34 +02:00
Irmen de Jong 8e6a27d7c1 @bank now also allows the name of a subroutine for dynamic / overlay banking 2026-06-12 23:47:16 +02:00
Irmen de Jong d0edc0b484 prevent TCO with defer blocks 2026-06-12 03:05:26 +02:00
Irmen de Jong ccd3d94ec0 prevent TCO with defer blocks 2026-06-12 03:02:58 +02:00
Irmen de Jong 98355d3850 avoid shouldContain asserts on large strings in tests to avoid massive log dumps 2026-06-12 02:48:41 +02:00
Irmen de Jong 0e53113b85 Merge branch 'v12.2.1'
# Conflicts:
#	gradle.properties
2026-06-11 23:12:28 +02:00
Irmen de Jong e9a3ed457e fix optimizer problem that removed var inits for pointer vars in certain cases. Fixes #225 2026-06-11 22:37:51 +02:00
Irmen de Jong 6001da30d8 add comments to float library routines about the Z flag wrapper routines
(cherry picked from commit c55f5e4b50)
2026-06-11 21:50:25 +02:00
Irmen de Jong c55f5e4b50 add comments to float library routines about the Z flag wrapper routines 2026-06-11 21:49:51 +02:00
Irmen de Jong e983c0c2b9 fixed the c64 float heisenbug
(cherry picked from commit 532bf13057)
2026-06-11 21:40:42 +02:00
Irmen de Jong 532bf13057 fixed the c64 float heisenbug 2026-06-11 21:29:06 +02:00
Irmen de Jong de706e79de add friendly error for invalid 2D array syntax 2026-06-10 04:14:32 +02:00
Irmen de Jong a19f337ebe update prog8-coder skill 2026-06-10 02:49:48 +02:00
Irmen de Jong 041a904e44 fix serialdownload size test 2026-06-09 19:24:02 +02:00
Irmen de Jong 131134d24b fix daemon output path 2026-06-09 02:06:07 +02:00
Irmen de Jong 97fb5298a8 fix daemon output path 2026-06-09 01:59:06 +02:00
Irmen de Jong b580a177d4 serial module now has configurable baud rate, and some uart tweaks 2026-06-09 01:46:46 +02:00
Irmen de Jong a0b1ca5d9f Merge branch 'next-version-12.3' 2026-06-08 21:35:53 +02:00
Irmen de Jong 3a291c56b4 ci: split build step into compile, test, and package for better granularity 2026-06-08 21:07:25 +02:00
Irmen de Jong ed41d5dbc5 update libs and Kotlin
remove docs and examples imls
2026-06-08 20:58:27 +02:00
Irmen de Jong 628bd41740 update kotest 2026-06-08 20:43:54 +02:00
Irmen de Jong de988f72c6 switch from IDEA iml to Gradle build config, this finally fixes Kotest integration as well 2026-06-08 20:43:54 +02:00
Irmen de Jong 869c5ef544 move -srcdirs directories in front of everything else.
add -traceimports option to trace module imports.
2026-06-08 20:35:39 +02:00
Irmen de Jong 17ee233e8b tweak import path search order and document it 2026-06-08 20:34:17 +02:00
Irmen de Jong f2d1dffbe0 preparing for 12.2 release 2026-06-08 20:15:19 +02:00
Irmen de Jong 4008280a62 fix 64tass output order in daemon 2026-06-07 02:45:52 +02:00
Irmen de Jong 2e7235ec02 fix daemon mode error reporting paths 2026-06-07 02:31:06 +02:00
Irmen de Jong a4330f5048 fix -emu program name problem with -daemon 2026-06-07 00:34:35 +02:00
Irmen de Jong f65aeff231 -emu works again together with -daemon 2026-06-07 00:08:07 +02:00
Irmen de Jong 505243aff7 fix a optimizer crash 2026-06-06 23:29:16 +02:00
Irmen de Jong d0f2fc13ba add dummy rambank() and rombank() support for virtual target 2026-06-06 23:26:47 +02:00
Irmen de Jong 53d51dcd38 improve -libsearch by adding fuzzy search fallback 2026-06-06 19:28:53 +02:00
Irmen de Jong c1aa0e49fe fix a possible vera registers clobbering issue when using psg2 from irq handler 2026-06-06 17:30:08 +02:00
Irmen de Jong e1e72d4317 fix crash when using const ubyte pointer as string argument 2026-06-06 17:12:15 +02:00
Irmen de Jong feec343cbc made -daemon more robust 2026-06-06 12:41:15 +02:00
Irmen de Jong 5cdc4233f3 ideas & update prog8-coder skill 2026-06-04 01:16:41 +02:00
Irmen de Jong 1ae89d18cc prevent string deduplication for struct initializers 2026-06-03 01:43:50 +02:00
Irmen de Jong eb19d00a1e prevent faulty inlining of subs with register-aliased parameters 2026-06-02 23:14:55 +02:00
Irmen de Jong e01ee10ef0 fix output binary path displayed to the user 2026-06-02 21:05:03 +02:00
Irmen de Jong f4b3cb12e1 history 2026-06-01 23:19:31 +02:00
Irmen de Jong 77ced60ad6 fix psg2 release timing to make it linear just as attack.
clarify envelope timing details in docs.
2026-06-01 22:58:42 +02:00
Irmen de Jong 6fad90af6e long for loop warning 2026-05-31 23:42:58 +02:00
Irmen de Jong 3076318d7e implement missing for loops on long rages for 6502 2026-05-31 23:38:35 +02:00
Irmen de Jong 9c99110738 doc 2026-05-31 23:07:16 +02:00
Irmen de Jong 1457d36997 beta3 2026-05-31 17:47:37 +02:00
Irmen de Jong 43fb9296dd clarify serial module docs 2026-05-31 17:46:33 +02:00
Irmen de Jong 8bf744cc4a fix some bitwise expression optimization bugs 2026-05-31 07:35:32 +02:00
Irmen de Jong f1f5f070cf fix invalid asm for some in-place long operations on zp vars 2026-05-31 05:14:33 +02:00
Irmen de Jong 191a240d0d adjust extsub jump table offsets to more closely match reality 2026-05-30 23:42:52 +02:00
Irmen de Jong cafb2e355b implement long loop counter support 2026-05-30 23:36:08 +02:00
Irmen de Jong c99c5751f8 tweak serial 2026-05-30 21:11:19 +02:00
Irmen de Jong 435b7e56fa IR VM refactor to reduce class size 2026-05-29 23:14:44 +02:00
Irmen de Jong 14f13fac53 IR tweaks 2026-05-29 22:17:56 +02:00
Irmen de Jong f8b5037924 spelling corrections 2026-05-29 20:58:06 +02:00
Irmen de Jong d2870cb02e implement hex mode file transfer in serial module
as an attempt to have a working file transfer because the binary mode is still failing right now
2026-05-29 20:04:40 +02:00
Irmen de Jong 8e43fc426c IR: handle extsub with banked addresses properly 2026-05-28 03:25:04 +02:00
Irmen de Jong ba1b6e127a IR: properly consume registers for void targets 2026-05-28 00:57:00 +02:00
Irmen de Jong 4f36a6958c fix a few if-expression typecast issues 2026-05-28 00:55:15 +02:00
Irmen de Jong df8f08b4ab more serial tweaks to attempt to fix binary downloads 2026-05-27 23:42:55 +02:00
Irmen de Jong fd5746d3fc tweak the serial module a bit for more robustness 2026-05-27 02:22:35 +02:00
Irmen de Jong 6ab74fd51d don't erroneously allow taking the address of const values 2026-05-27 00:58:01 +02:00
Irmen de Jong 245082ca1d plain console output in github action builds 2026-05-24 12:40:43 +02:00
Irmen de Jong 845e58ea10 64tass install step 2026-05-24 12:33:47 +02:00
Irmen de Jong 2a5dbc1ef7 update project urls that have been moved from github to codeberg 2026-05-24 12:29:09 +02:00
Irmen de Jong 02a1deb1e0 update github build actions 2026-05-23 00:13:14 +02:00
Irmen de Jong 3ba8e5de10 Add serial download example for CX16 and fix serial module import 2026-05-23 00:05:40 +02:00
Irmen de Jong 819d1b4af7 Add serial library for CX16 with UART and ZiModem support
version 12.2-BETA2
2026-05-22 23:52:58 +02:00
Irmen de Jong cdd3d37376 update asmsub inlining behavior and tests: improve support for parameterized calls 2026-05-22 04:17:43 +02:00
Irmen de Jong d0bc01d618 improve inliner: support slightly more complex return expressions 2026-05-22 03:15:57 +02:00
Irmen de Jong ffab73334a enforce errors for empty enum and struct declarations, add corresponding tests 2026-05-22 02:39:09 +02:00
Irmen de Jong 83aeb8846c add support for private keyword for structs, enums, aliases, variables, and subroutines 2026-05-22 02:27:38 +02:00
Irmen de Jong 3a2087310b background daemon compiler mode via -daemon option 2026-05-21 19:08:29 +02:00
Irmen de Jong 61b616701e fix -watch mode to not use stale cached files on subsequent runs 2026-05-20 22:54:52 +02:00
Irmen de Jong bddf872906 better romable awareness in IR 2026-05-20 19:54:12 +02:00
Irmen de Jong 48214a6fdb more IR peephole optimizations, added some implementation plans to the todo 2026-05-19 00:26:35 +02:00
Irmen de Jong b7340856df fix long expression evaluation bug clobbering R14R15 intermediary results 2026-05-18 20:07:23 +02:00
Irmen de Jong 2685854113 memory("name") with only 1 arg now allowed to refer to the actual memory reservation call elsewhere (with all three name, size, align args). 2026-05-16 23:24:41 +02:00
Irmen de Jong ad92f39469 fix error message for conflicting memory() calls 2026-05-16 23:01:49 +02:00
Irmen de Jong d3fe041e01 update kotlin and ksim65 2026-05-16 15:33:03 +02:00
Irmen de Jong f32bac5004 implement 6502 long division routine 2026-05-16 15:04:09 +02:00
Irmen de Jong 9a8e4ac105 lineclip.inside() at top level to check a single point 2026-05-15 21:53:13 +02:00
Irmen de Jong 78857e5c22 pet32: diskio.list_filenames() now checks buffer size 2026-05-15 19:38:45 +02:00
Irmen de Jong 1bb828875d improved vm waitvsync 2026-05-15 18:05:01 +02:00
Irmen de Jong 82aa67ffda lineclip module added 2026-05-15 17:45:13 +02:00
Irmen de Jong 7c44835772 const folding is always done even when -noopt is used 2026-05-15 12:22:52 +02:00
Irmen de Jong 8b9c63fe90 lineclip 2026-05-15 00:31:27 +02:00
Irmen de Jong e77fcc8c16 fix an unsigned long problem 2026-05-10 21:29:42 +02:00
Irmen de Jong 08c12131d4 tweak the rules for turning pointers into consts 2026-05-10 19:51:06 +02:00
672 changed files with 100378 additions and 20283 deletions

No files matched your search

+107 -134
View File
@@ -1,166 +1,139 @@
---
name: asm6502-coder
description: Write 6502/65C02 assembly code using 64tass syntax, for use within Prog8 programs
license: MIT
compatibility: opencode
description: Writing or understanding 6502/65C02 assembly in 64tass syntax, standalone or embedded in Prog8
---
# 65(C)02 Assembly Coder Skill
# 6502 Assembly Coder
You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.asm` files or embedded in a Prog8 program (inside `%asm {{ }}` blocks or `asmsub` routines). Follow all rules below.
Use 64tass syntax, not ca65 or another assembler. Keep answers concise and
practical. This applies to standalone `.asm` files and Prog8 `%asm {{ }}` or
`asmsub` blocks.
## Debugging Generated Assembly
- The prog8 compiler outputs `*.asm` (assembly source) and (with -asmlist option) also `*.list` (full listing with address/symbols) when compiling. Inspect these to debug generated code, verify optimizations, and trace instruction sequences.
## Syntax
## Assembler: 64tass Syntax
- **NOT ca65/cc65** or other assemblers. Key differences:
- `.proc` / `.pend` for procedures (scoping)
- `_label` for local labels (prefixed with underscore, scoped to `.proc`)
- **Anonymous labels**: defined as `+` (forward) or `-` (backward) at the start of a line. Reference them in branches using `+`, `++`, `+++` etc. (first/second/third *upcoming* anonymous forward label) and `-`, `--`, `---` etc. (first/second/third *preceding* anonymous backward label).
- Crucial: `+` refers to the NEXT upcoming `+` label, `++` refers to the ONE AFTER that, etc.
- `-` refers to the MOST RECENT `-` label, `--` to the one before that, etc.
- When a `+` label is passed, the forward-reference count resets (so `++` then refers to the next one after that new label)
- Example:
```asm
- dex ; backward label '-'
bne - ; branch to most recent '-' (the dex above)
ldx #5
+ dex ; forward label '+'
bne + ; branch to this same '+' (forward)
sta $400
+ lda #0 ; second '+'
bne ++ ; branch to the '++' below
rts
++ inc $d020 ; third forward label
```
- Data directives: `.byte`, `.word`, `.dword`
- Equates: `label = value` (not `label .equ value` or `#define`)
- Zero-page variables defined with `=`
- `.text` for inline string data
- Labels begin in column 1. Instructions are indented at least four spaces.
- Use lowercase opcodes and operands, two spaces between opcode and operand,
and two spaces before end-of-line comments.
- Hex is `$1234`, binary is `%1010`, decimal is `123`.
- Use `.byte`, `.word`, `.dword`, `.fill`, `.text`, and equates such as
`name = value`.
- Sections use `.section name` and `.send name`.
- Procedures use `.proc` and `.pend`; labels beginning with `_` are local to a
procedure.
- Conditional assembly uses `.if`, `.elsif`, `.else`, and `.endif`.
## Instructions
- **Instructions like `rol`, `ror`, `asl`, `lsr`, `php`, `pla` require an explicit operand for accumulator**: write `rol a`, `ror a`, not just `rol`/`ror`
- Standard 6502 addressing modes: implied, immediate (`#`), zero-page (`zp`), zero-page,X (`zp,x`), absolute (`abs`), absolute,X (`abs,x`), absolute,Y (`abs,y`), indirect (`(abs)`), indirect,X (`(zp,x)`), indirect,Y (`(zp),y`), relative (branches), accumulator
- Branches: `bne`, `beq`, `bmi`, `bpl`, `bcs`, `bcc`, `bvs`, `bvc` (relative, max +127/-128 bytes)
- Jumps: `jmp` (absolute or indirect), `jsr`/`rts` (subroutine call/return)
- No `push`/`pop` mnemonics — use `pha`/`pla` (byte) and `txa`/`phx`/`plx`/`tay`/`phy`/`ply` for registers
## Anonymous Labels
## 6502 vs 65C02
- **CX16 target only**: can use 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `clr`, `ina`, `dea`, `cmp (zp)`, `dec`/`inc abs,x` etc.
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra`
- Check the target before using 65C02-specific instructions
Anonymous label definitions are always a single `+` or `-` in column 1.
References use repeated signs:
## Calling Convention / Register Conventions
- **Accumulator (A)**: 8-bit, used for most arithmetic, data movement, return values
- **X register**: 8-bit, often used for indexing, loop counters
- **Y register**: 8-bit, often used for indirect addressing index
- **Processor Status (P)**: flags — carry (C), zero (Z), negative/N (sign bit 7), overflow (V), decimal (D), interrupt (I), break (B)
- **No caller-saved vs callee-saved convention** — list all modified registers in `clobbers (A, X, Y)` when writing `asmsub`
- The CPU stack (SP, $0100-$01FF) is limited (usually ~128 bytes free). Do not overflow it
- `+` is the next upcoming `+` definition, `++` the second, `+++` the third.
- `-` is the most recent `-` definition, `--` the preceding one, and so on.
- Passing a `+` definition resets the forward-reference count.
## Assembly within Prog8 Programs
```asm
- dex
bne - ; most recent '-' above
ldx #5
+ dex ; first upcoming '+'
bne + ; this '+'
sta $400
+ lda #0 ; second '+' definition
bne ++ ; next '+' after this one
rts
+ inc $d020 ; third '+' definition
```
### `%asm {{ }}` blocks
- Embed arbitrary 64tass assembly directly in your Prog8 source
- Access Prog8 symbols using their prefixed names (see below)
- Can be placed inside subroutines or at block level
## CPU Differences
### `asmsub` (assembly subroutine)
- For kernel (ROM) routines or low-level assembly
- Parameters passed via registers: `@A`, `@X`, `@Y`, `@AX` (A low, X high), `@AY` (A low, Y high), `@R0`-`@R15`, `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
- Return value: `-> type @register` — also via `@Pz`/`@Pc` for flags
- Clobbers: `clobbers (A, X, Y)` — MUST list all modified registers
- **Parameter names are documentation only** — use the actual registers in assembly, NOT parameter names
- Create symbolic aliases at assembly top for clarity: `x1 = cx16.r0`, `y1 = cx16.r0L`
- `cx16` is the only Prog8 target that supports 65C02 instructions such as
`stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `wai`, `stp`, and
`bit #imm`. C64, C128, and PET32 use the original 6502.
- Rockwell instructions (`rmb`, `smb`, `bbr`, `bbs`) are not available.
- Use an explicit `a` for accumulator forms such as `rol a`, `lsr a`, `inc a`,
and `dec a`.
- Branches are relative. Prog8's assembler invocation enables `--long-branch`
for out-of-range branches.
- `lda`, `ldx`, and `ldy` do not affect carry. `cmp` sets carry for
`register >= operand`.
- On NMOS 6502, `jmp ($xxff)` wraps the high-byte fetch within the page.
- `BRK` skips the byte after its opcode. The byte is commonly a handler
signature.
- `bit` memory forms set N and V from the operand and Z from `A AND operand`.
65C02 `bit #imm` sets only Z.
- Decimal mode can persist into NMOS IRQ handlers. Use `cld` at handler entry
and explicitly control D before `adc` or `sbc`.
## Prog8 Integration
`asmsub` parameter names are documentation only. Use the actual registers or
define assembly aliases, and list every modified register in `clobbers`.
Example:
```prog8
asmsub line(uword x1 @R0, ubyte y1 @A, uword x2 @R1, ubyte y2 @Y) clobbers (A, X, Y) {
asmsub increment(uword value @R0) clobbers (A, X) {
%asm {{
x1 = cx16.r0
x2 = cx16.r1
lda x1 ; use alias, not "_x1"
lda cx16.r0L
clc
adc #1
sta cx16.r0L
}}
}
```
### `asmsub` parameter annotation reference
| Annotation | Register | Size |
|------------|----------|------|
| `@A` | Accumulator | 8-bit |
| `@X` | X register | 8-bit |
| `@Y` | Y register | 8-bit |
| `@AX` | A (low) + X (high) | 16-bit |
| `@AY` | A (low) + Y (high) | 16-bit |
| `@R0`-`@R15` | cx16 virtual registers | 16-bit each |
| `@FAC1`/`@FAC2` | Floating-point accumulators | 5-byte float |
| `@Pc` | Carry flag | bool |
| `@Pz` | Zero flag | bool |
Annotations include `@A`, `@X`, `@Y`, `@AX`, `@AY`, `@R0`-`@R15`, `@FAC1`,
`@FAC2`, `@Pc` (carry), and `@Pz` (zero). Unmapped parameters are accessed as
`p8v_name`.
## Accessing Prog8 Symbols from Assembly
All Prog8 symbols are prefixed when accessed from assembly:
Prog8 symbols use these prefixes: `p8v_` variables and parameters, `p8s_`
subroutines, `p8b_` blocks, `p8c_` constants, `p8l_` labels, and `p8t_` struct
types. Fully qualified names may look like
`p8b_main.p8s_helper.p8v_local`. `%option no_symbol_prefixing` disables the
prefixes. Split word arrays use `_lsb` and `_msb` symbols.
| Prefix | Refers to | Example |
|--------|-----------|---------|
| `p8v_` | Variables, parameters | `p8v_myvar` |
| `p8s_` | Subroutines | `p8s_mysub` |
| `p8b_` | Blocks | `p8b_myblock` |
| `p8c_` | Constants, enum members | `p8c_myconst`, `p8c_MyEnum_Member` |
| `p8l_` | Labels | `p8l_mylabel` |
| `p8t_` | Struct types | `p8t_MyStruct` |
| `p8_` | Other symbols | |
## Zeropage and Memory
- **Fully qualified**: e.g., `p8b_myblock.p8v_myvar`, `p8b_myblock.p8s_mysub.p8v_localvar`
- Within a `.proc` (subroutine) in your assembly, short names often work (assembler scoping)
- **`%option no_symbol_prefixing`**: disables all prefixes. Stdlib modules (`cbm`, `cx16`, `txt`) use this — you can write `cbm.CHROUT` directly
- **Split word arrays**: two separate byte arrays — append `_lsb` and `_msb` to the name: `p8v_myarray_lsb`, `p8v_myarray_msb`
- Never hardcode zeropage addresses. Use `P8ZP_SCRATCH_B1`,
`P8ZP_SCRATCH_REG`, `P8ZP_SCRATCH_W1`, `P8ZP_SCRATCH_W2`, and
`P8ZP_SCRATCH_PTR`, or the appropriate `cx16.r0`-`cx16.r15` symbols.
- Scratch variables are not necessarily consecutive. CX16 virtual registers
are consecutive and have `L` and `H` byte names.
- Allocate additional temporary storage in BSS.
- The hardware stack occupies `$0100-$01ff` and is limited. Do not overflow
it.
- Prog8 words are little-endian: load the low byte first, then the high byte.
## Zeropage Usage
- Do NOT use arbitrary zeropage locations. Only use these predefined scratch variables:
- `P8ZP_SCRATCH_B1` (byte)
- `P8ZP_SCRATCH_REG` (byte)
- `P8ZP_SCRATCH_W1` (word)
- `P8ZP_SCRATCH_W2` (word)
- `P8ZP_SCRATCH_PTR` (word)
- On CX16: virtual registers `cx16.r0`-`cx16.r15` are in zeropage (and their low/high bytes: `cx16.r0L`, `cx16.r0H`, etc.)
- Virtual registers `cx16.r0`-`cx16.r15` are available on ALL targets, but only on CX16 in zeropage
- Assume the scratch variables are not consecutive in zeropage. The CX16 virtual registers ARE consecutive in memory though.
- For additional temporary storage, allocate regular variables in BSS
## Common 6502 Patterns
### Looping (downto with BNE)
```asm
ldx #count
loop ; do work here
dex
bne loop ; loop while X != 0
```
### Indirect indexed read (table of data)
```asm
ldy #index
lda (ptr),y ; read byte at address stored in zp ptr + Y
```
### 16-bit arithmetic (word add)
```asm
clc
lda word1_lo
adc word2_lo
sta result_lo
lda word1_hi
adc word2_hi
sta result_hi
lda word1
adc word2
sta result
lda word1+1
adc word2+1
sta result+1
```
### Calling a Prog8 subroutine from assembly
## Common Patterns
```asm
jsr p8s_myblock.p8s_mysub
ldy #index
lda (ptr),y
jsr p8s_main.p8s_helper
```
### Reading a word variable
```asm
lda p8v_myword ; loads LSB
ldy p8v_myword+1 ; loads MSB (word variables are stored LSB-first)
Avoid self-modifying code unless it is explicitly required. It cannot run
from ROM and complicates debugging and tooling. Prefer lookup tables, RAM
vectors, or alternative algorithms.
## Tools
Prog8 invokes 64tass with `--ascii --case-sensitive --long-branch -Wall` plus
the target output option such as `--cbm-prg`. For manual assembly:
```bash
64tass --ascii --case-sensitive --long-branch -Wall --cbm-prg \
-o myprogram.prg myprogram.asm
```
For debugging, add `--vice-labels --labels=labels.txt` and/or
`--list=listing.txt`.
+150
View File
@@ -0,0 +1,150 @@
---
name: m68k-coder
description: Writing or understanding Motorola 68000/68020 assembly in vasm mot syntax, standalone or embedded in Prog8
---
# M68K Assembly Coder
Use Motorola syntax for `vasm`, not Devpac or other assemblers. Keep answers
concise and practical.
## Syntax
- Labels begin in column 1. Instructions are indented at least four spaces.
- Use lowercase opcodes and operands, two spaces between opcode and operand,
and two spaces before end-of-line comments.
- Use `.b`, `.w`, and `.l` for byte, word, and long operations. Do not rely on
default sizes.
- Hex is `$1234`, binary is `%1010`, decimal is `123`, and characters use
`#'A'`.
- Local labels start with `.`. Equates use `name = value`.
- Data directives are `dc.b`, `dc.w`, `dc.l`; allocation directives are
`ds.b`, `ds.w`, `ds.l`.
- Sections use forms such as `SECTION .text,code` and `SECTION .bss,bss`.
```asm
moveq #0,d0
move.w value,d0
add.l d1,d0
.loop
dbra d0,.loop
```
## FPU Size Suffixes
FPU instructions that access memory **must** include a size suffix: `.s`
(single, 4 bytes), `.d` (double, 8 bytes), or `.x` (extended, 12 bytes).
Without a suffix, vasm defaults to `.x`, reading 12 bytes and corrupting
results when the operand is smaller.
**Use `.s` with memory operands:**
```asm
fmove.s value(pc),fp0 ; load float from memory
fcmp.s other(pc),fp0 ; compare float in memory
ftst.s value(pc) ; test float in memory
```
**Omit the suffix for register-to-register operations:**
```asm
fadd fp1,fp0 ; fp0 = fp0 + fp1
fcmp fp1,fp0 ; compare fp0 vs fp1
ftst fp0 ; test fp0
fintrz fp0,fp0 ; truncate fp0
```
The FPU registers are always 80-bit internally, so no size suffix is needed
when both operands are registers.
## Architecture
- Registers `D0-D7` and `A0-A7` are 32-bit; `A7` is the stack pointer.
- Pointers are 32-bit and must normally be accessed with `.l`.
- Memory is big-endian: `$1234` is stored as `$12,$34` and a long's low byte
is at offset `+3`.
- Word and long accesses must be even-aligned on 68000. 68020 permits
misalignment but it is slower.
- The 68000 (`amiga500`) has only word multiply/divide and no scaled indexing,
bit-field, or 32-bit multiply/divide instructions. The 68020 (`qemu68k`)
adds those features and `extb.l`.
- On 68000, sign-extend a byte with `ext.w` followed by `ext.l`.
- `move.b` and `move.w` to a data register do not clear its upper bits. Like
`move.w` only writes the lower 16 bits, `move.b` only writes the lower 8 bits;
both leave all other bits of the register untouched. So zero-extending a byte
to long requires `moveq #0,d0` first, then `move.b src,d0`, then `move.l d0,dst`
(or mask with `and.l #$ff` / `and.l #$ffff`). Relying on `move.b` to clear the
upper bits is a common bug that corrupts values.
- `movea` does not set condition codes; `move` does. Keep the stack word
aligned and push words or longs, never bytes.
## Registers and Calls
- Return values use `D0` (primary), `D1` and `D2` (additional), or `A0` for a
pointer.
- `D0-D1` and `A0-A1` are scratch registers. Preserve registers required by
the surrounding calling convention, especially `D2-D7` and `A2-A6`.
- `move.l d0,-(sp)` pushes and `move.l (sp)+,d0` pops.
## Prog8 Integration
Assembly can appear in `%asm {{ }}` blocks or `asmsub` routines. `asmsub`
parameter names are documentation only: use the actual annotated registers.
Declare every modified register in `clobbers`.
```prog8
asmsub add100(uword value @D0) -> uword @D0 clobbers (D0) {
%asm {{
add.w #100,d0
}}
}
```
Do not use `%option no_sysinit` on `amiga500` or `qemu68k` targets when the goal
is to create normal runnable programs. It skips critical startup logic including
library initialization and CLI argument handling, causing programs to crash on
start.
Supported annotations include `@D0`-`@D7`, `@A0`-`@A6`, virtual `@R0`-`@R15`,
floating-point `@FP0`-`@FP7`, and flags `@Pc` and `@Pz`.
Prog8 assembly symbols are prefixed:
| Prefix | Symbol |
| --- | --- |
| `p8v_` | variable or parameter |
| `p8s_` | subroutine |
| `p8b_` | block |
| `p8c_` | constant or enum member |
| `p8l_` | label |
| `p8t_` | struct type |
Use fully qualified names when needed, such as
`p8b_main.p8v_value`. `%option no_symbol_prefixing` disables prefixes.
```asm
move.l p8v_pointer,a0
move.w p8v_word,d0
jsr p8s_main.p8s_helper
```
## Useful Patterns
```asm
lea source,a0
lea dest,a1
.copy
move.b (a0)+,(a1)+
bne.s .copy
```
Use `moveq` for small constants, `addq`/`subq` for constants 1 through 8, and
`lea` for address arithmetic. Check divisors for zero and division overflow.
## Tools
For QEMU 68k, Prog8 invokes `vasmm68k_mot` with `-m68020 -m68881 -Felf
-opt-speed -warnunaligned -ldots -spaces`, then links with `vlink`. For raw
binary output use `-Fbin` and `-join=0x<addr>`. Add `-L listing.txt` when a
listing is needed.
Prog8 runs `amiga500` programs with `vamos --cpu 68000` and `qemu68k` programs
with `qemu-system-m68k -M virt -cpu m68020 -m 1M -kernel program.elf -nographic`.
+116 -197
View File
@@ -1,221 +1,140 @@
---
name: prog8-coder
description: Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets)
license: MIT
compatibility: opencode
description: Writing or understanding Prog8 programs and Prog8 IR
---
# Prog8 Coder Skill
# Prog8 Coder
You are working with **Prog8** source code (`.p8` files) or its Intermediate Representation (`.p8ir` files). Prog8 targets 8-bit systems (C64, CX16, C128, PET32) with the 6502 CPU, plus a `virtual` target for testing.
You are an expert Prog8 assistant. Keep answers concise and practical. This
skill covers `.p8` source and `.p8ir` intermediate representation for the
6502, M68000, and virtual targets.
Follow ALL the rules below carefully.
For full language reference, link users to these project documents rather than
repeating them here:
## General & Setup
- A program = a `main` block containing a `start` subroutine entry point, plus optional other subroutines/blocks
- Add `%zeropage basicsafe` at the top of your program to allow clean return on exit (instead of resetting the machine/emulator)
- Module imports: `%import modulename` — no `as` aliasing. Use the module's defined prefix (e.g., `%import textio` → `txt.xxx`)
- **Virtual target for testing**: Use `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir` for fast testing
- **Fast syntax check**: `prog8c -check input.p8` — does NOT produce output files (.prg, .asm, etc.), just errors
- **Output directory**: `prog8c -out outdir input.p8` (default: same dir as source)
- **Compilation outputs**: `*.prg` (program binary), `*.asm` (assembly listing), `*.list` (full listing), `*.p8ir` (IR for VM), `*.vice-mon-list` (Vice debug symbols)
- `.p8ir` files contain the **Intermediate Representation** — a sequence of named chunks, each containing typed instructions and virtual registers. This is a target-independent representation of the program, executable by the built-in Virtual Machine via `prog8c -vm file.p8ir`. Useful for debugging the compiler's code generation path without involving 6502 assembly.
- **Debugging switches**: `-noopt` (disable optimizations), `-printast1` (parsed AST), `-printast2` (optimized simple AST), `-compareir` (compare IR outputs)
- **TODO list**: Check `docs/source/todo.rst` to see what features are NOT yet implemented
- **Test programs**: add `%zeropage basicsafe` and `%option no_sysinit` at top
- **`sys` module**: always available, no import needed
- **CX16 debugging**: Add `%encoding iso`, call `txt.iso()` in `start()`, end with `sys.poweroff_system()`. For emulator: `x16emu -echo iso -run -prg input.prg 2>&1 | grep ...`
- Program structure and syntax: `docs/source/programming.rst`
- Variables, datatypes, arrays, and tags: `docs/source/variables.rst`
- Structs and pointers: `docs/source/structpointers.rst`
- Compilation and compiler options: `docs/source/compiling.rst`
- Standard library: `docs/source/libraries.rst`
- Binary libraries and `extsub`: `docs/source/binlibrary.rst`
- Target differences: `docs/source/targetsystem.rst`
- Known limitations: `docs/source/todo.rst`
- Target API signatures: `docs/source/_static/symboldumps/skeletons-<target>.txt`
## Datatypes & Variables
- Primitives: `bool`, `byte`, `ubyte`, `word`, `uword`, `long`, `float`, `str`
- `ubyte`/`uword` = unsigned; `long` = signed 4-byte; `float` = 5-byte MS format; `str` = 0-terminated ubytes (max 255 chars)
- **float requires `%import floats`** at top of file, else compiler errors
- Arrays: max 256 bytes (512 for split word arrays). For larger data, use `memory()` + pointers
- `memory(name, size)` returns a `uword` address to a statically reserved block of memory
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable)
- Struct fields: only simple types + `str` allowed. NO arrays as fields. `str` in struct = `^^ubyte`
- Word/pointer arrays split into LSB/MSB by default. Override with `@nosplit`
- **No call stack**: all variables statically allocated. No recursion without manual stack management
- Variables zero-initialized (globals at start, locals on subroutine entry)
- `@shared` marks variables as "used by external code" (assembly), prevents optimization
- `@zp`/`@requirezp`: use sparingly — only for pointers (limited zeropage space)
- Pointer-like typed pointers (`^^type`) support C-style scaled arithmetic; `uword` pointers always treat element as 1 byte
- `&` = untyped address (uword); `&&` = typed pointer
- Available zeropage scratch: `P8ZP_SCRATCH_B1`, `P8ZP_SCRATCH_REG`, `P8ZP_SCRATCH_W1`, `P8ZP_SCRATCH_W2`, `P8ZP_SCRATCH_PTR` — and cx16 virtual registers R0-R15 on all targets
- Virtual registers (`cx16.r0`–`cx16.r15`): global 16-bit, NOT preserved across calls. R12-R15 may be clobbered by long ops. Save/restore in IRQs with `cx16.save_virtual_registers()`/`cx16.restore_virtual_registers()`
- Math performance: integer trig (`math.sin8`, `math.cos8`) uses fast LUTs; float trig (`floats.sin`/`cos`) is much slower
## Essentials
## Recursion & Stack Management
No call stack for variable storage — recursion overwrites locals. To handle it:
1. **CPU hardware stack**: `push()`/`pushw()`/`pushl()`/`pushf()` and `pop()`/`popw()`/`popl()`/`popf()`. Save/restore locals around recursive calls
2. **Software stacks**: `buffers.stack` (uword) / `buffers.smallstack` (ubyte). Both provide `push_b()`/`push_w()` and `pop_b()`/`pop_w()`
3. **Iterative rewrite (preferred)**: Many recursive algorithms work as `repeat` loops with explicit bounds — avoids all stack overhead
- A program normally has a `main` block with a parameterless `start` entry
subroutine. Other blocks and subroutines may be added.
- `%import modulename` imports a module without aliases. Use its defined prefix,
such as `%import textio` followed by `txt.print(...)`.
- `sys` is always available and needs no import.
- `.p8ir` is target-independent IR and can be run with
`prog8c -vm program.p8ir`.
- Use `%zeropage basicsafe` in test programs when a clean return is needed.
- Find library routines with `prog8c -libsearch <regex>` or extract them with
`prog8c -libdump <dir>`.
## Strings, Arrays & Pointers
- `str` / array: max 256 bytes. `long[]` limited to 64 entries (64x4=256). `str[]` for string arrays: `str[5] names = ["a","b","c","d","e"]`
- 2D arrays: `type[rows][cols] name`, access `name[r][c]`. Flat init list only (no nested `[[...]]`). Total size still ≤ 256 bytes
- str/array passed as pointer to subroutine (receiving subroutine gets `^^ubyte` or `^^element`)
- **No const pointers** or pointer-to-pointer currently supported
- **Parsing limitation**: `pointer[index].field` as assignment target needs `^^`: `pointer[index]^^.field = value`
- **Pre-allocate buffers**: `str buffer = "." * 50` (empty `""` allocates nothing — `strings.append()` will fail)
- **No reassignment**: can't `buffer = "new text"` after declaration. Use `strings.copy()`/`strings.ncopy()`
- String concat is expensive on 6502. Prefer separate prints over concatenation
- **Efficient buffer iteration**: prefer `ptr++` + `@(ptr)` over `@(buffer + offset)`. Exception: if offset is a `ubyte` (≤ 255), `buffer[offset]` works fine
- `@(ptr)` = peek/poke byte. For words: `peekw`/`pokew`, longs: `peekl`/`pokel`, floats: `peekf`/`pokef`, bools: `peekbool`/`pokebool`
- Use `len(array)` instead of hardcoded sizes
- Array indexing is 0-based: `arr[0]` is first element
- Static memory only — real dynamic allocation impossible, but can emulate with a simple arena allocator over a `memory()` slab
For CX16 emulator programs, use `%encoding iso`, call `txt.iso()` in `start`,
and finish with `sys.poweroff_system()`. CBM targets use PETSCII by default;
avoid uppercase in test output unless intentional graphics. The virtual target
uses ISO when configured with `%encoding iso` and `txt.iso()`.
## Logic & Control Flow
- Logical operators (short-circuit, bool only): `and`, `or`, `xor`, `not`
- In `a and b`: if `a` is false, `b` is NOT evaluated
- In `a or b`: if `a` is true, `b` is NOT evaluated
- Important when `b` has side effects
- Bitwise operators: `&`, `|`, `^`, `~`, `<<`, `>>`
- Bit rotation: `rol()`/`ror()` (through carry), `rol2()`/`ror2()` (no carry)
- CPU status flag branches: `if_cs`, `if_cc`, `if_z`, `if_nz` (compile to single 6502 branch instructions)
- Use `when` with choice blocks instead of multiple `if`
- If-expressions for simple value assignments based on a choice
- `defer` defers statement execution until scope exit
- `goto`, labels, jump lists allowed
- **Common mistake**: `and`/`or` for bitmasking — use `&`/`|` instead!
Do not use `%option no_sysinit` on `amiga500` or `qemu68k` targets when the goal
is to create normal runnable programs. It skips critical startup logic including
library initialization (DOS, graphics, intuition, timer) and CLI argument
handling, causing programs to crash on start. It is appropriate for library
modules, IRQ handlers, boot stubs, or code that deliberately avoids those
libraries.
## Loop Constructs
Prog8 supports these loop types. All support `break` and `continue` (except `unroll`).
## Types and Memory
### `for` loop — iterate over a range or array
- Loop variable **must be declared separately** before the `for` statement
- Works with `ubyte`, `byte`, `uword`, `word`, `long`, pointer types (NOT `float`)
- Iterates over ranges (`start to end`), descending ranges (`start downto end`), or arrays/strings
- Optional `step <constant>` for non-unit step sizes
- Loop variable value **after the loop is undefined** — don't rely on it
- Descending loops with `downto` usually produce more efficient 6502 code
```
ubyte i
for i in 20 to 155 {
; body
break ; exit loop
continue ; next iteration
}
- Primitive types include `bool`, `byte`, `ubyte`, `word`, `uword`, `long`,
`float`, `str`, and `pointer`.
- `pointer` is target-sized: 16-bit on 6502 targets, 32-bit on `amiga500`,
`qemu68k`, and `virtual`. Use it for portable addresses, not `uword` or
`long` chosen by assumption.
- M68000 targets are big-endian and use 32-bit pointers. Load the `m68k-coder`
skill for assembly details.
- `float` requires `%import floats`.
- Variables are statically allocated and zero-initialized. There is no normal
call stack for locals, so recursion overwrites locals unless an explicit
hardware or software stack is used. Iterative rewrites are preferred.
- `memory(name, size)` reserves static memory and returns a `pointer`.
- `str` and arrays have compile-time byte budgets. On 6502 targets the usual
limit is 256 bytes; M68000 targets allow up to 32768 bytes. Use `memory()`
for larger data.
- Word arrays are split into LSB/MSB arrays by default. Use `@nosplit` when
contiguous storage is required.
- Variable tags follow the type and array dimensions, before the name:
; descending
for i in 155 downto 20 {}
for i in 155 to 20 step -1 {}
; iterate over array elements
uword[] fib = [0, 1, 1, 2, 3, 5, 8, 13]
uword num
for num in fib {
...
}
```prog8
ubyte[8] @shared vera_storage
uword @requirezp address
```
### `while` loop — repeat while condition is true
```
while condition {
; body
break
continue
}
```
Use `@shared` for values accessed externally by assembly. Use `@zp` and
`@requirezp` sparingly because zeropage is limited. Never assume virtual
registers survive calls. Long operations may clobber `R12-R15`.
### `do`-`until` loop — always executes body at least once
```
do {
; body
break
continue
} until condition
```
## Syntax Pitfalls
### `repeat` loop — repeat a fixed number of times (most efficient)
- Most efficient code generation — prefer over `for` when loop variable not needed
- Omit count for infinite loop (still supports `break`)
```
repeat 15 {
; body
break
continue
}
- Hex uses `$FF`, binary uses `%1010`, and casts use `expression as type`.
- There is no automatic type widening: `byte * byte` remains a byte. Cast
explicitly when a wider result is needed.
- `&` is an untyped address and `&&` is a typed pointer. Complex pointer field
assignments may require `ptr^^.field`.
- `and`, `or`, `xor`, and `not` are logical operators. Use `&`, `|`, `^`, and
`~` for bitwise operations.
- `and` and `or` short-circuit, so the right operand may not be evaluated.
- There is no `elif`, bare block, or semicolon statement separator. Semicolon
starts a comment. Prefer one statement per line and four-space indentation.
- `defer` executes registered statements in reverse registration order.
- Array indexing starts at zero. Use `len(array)` rather than hardcoded sizes.
- There is no function overloading. Call type-specific library routines such as
`txt.print_ub` or `txt.print_w`.
; infinite:
repeat {
; body
break if x==5
}
```
## Control Flow
### `unroll` loop — compile-time code duplication
- Not a real loop — duplicates body N times at compile time
- No `break`/`continue` allowed
- Only simple statements (assignments, calls) in body
- Constant iteration count required
```
unroll 80 {
cx16.VERA_DATA0 = 255
}
```
Prog8 supports `if`/`else`, `when`, `for`, `while`, `do`/`until`, `repeat`,
`unroll`, `break`, `continue`, `goto`, and labels. `unroll` duplicates a
constant-count body at compile time and does not support `break` or `continue`.
`repeat` is usually more efficient than `for` when no loop variable is needed.
Use `if_cs`, `if_cc`, `if_z`, and `if_nz` for direct CPU-flag branches.
## Subroutines & Return Values
- Symbols public by default; use `private` to restrict to block
- `inline` keyword for subroutines to suggest inlining
- No function overloading (except builtins). Cannot use builtin names (msw, lsw, msb, lsb, mkword, mklong, peek, peekw, peekl, etc.) as variable/sub names
- Can return 0, 1, or multiple values: `a, b, c = routine()`. Use `void` to skip: `void routine()`, `a, void, c = routine()`
- Nested subroutines access parent scope variables directly
## Subroutines and Assembly
## Assembly Subroutines (asmsub)
For kernel (ROM) routines or low-level assembly that gets arguments via registers.
- Parameter passing via registers: `@A` (accu), `@X`, `@Y`, `@AX`/`@AY` (16-bit), `@R0`-`@R15` (virtual regs), `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
- Return value: `-> type @register` (e.g., `-> ubyte @A`, `-> bool @Pz` for immediate branch use)
- Clobbers: `clobbers (A, X, Y)` — list all modified registers
- Parameter names are documentation only — use registers in assembly, not parameter names
- Create symbolic aliases at assembly start: `x1 = cx16.r0`, `x1_lo = cx16.r0L`
- Example:
```prog8
asmsub line(uword x1 @R0, ubyte y1 @A, uword x2 @R1, ubyte y2 @Y) clobbers (A, X, Y) {
%asm {{
x1 = cx16.r0
x2 = cx16.r1
lda x1
}}
}
```
- Symbol prefixes in assembly: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
- Fully qualified: `p8b_blockname.p8v_varname`, `p8b_blockname.p8s_subname.p8v_localvar`
- Within a `.proc`, short names often work. `%option no_symbol_prefixing` disables prefixes (used by `cbm`, `cx16`, `txt`)
- Split word arrays: append `_lsb` / `_msb` to variable name (e.g., `p8v_myarray_lsb`)
- CX16 target only: use 65C02 instructions (STZ etc). Others: 6502 only
- Assembly syntax: 64tass assembler. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
- Instructions like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`, not just `rol`
- Anonymous labels: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
- Subroutines can return zero, one, or multiple values:
`a, b = routine()` or `void routine()`.
- Avoid `private` unless requested. Prog8 symbols are public by default.
- `asmsub` bodies contain only one `%asm {{ ... }}` node. Parameters are type
checked and documented, but assembly must use the mapped registers. Declare
every modified hardware register in `clobbers`.
- `extsub` maps a signature to a fixed external address and has no body. Use
it for ROM, kernel, drivers, or binary-library routines.
- For inline assembly, load `asm6502-coder` for 64tass syntax, target-specific
instructions, zero-page rules, and symbol references. Load `m68k-coder` for
M68000 assembly.
## Standard Library
- Find routines, functions, variables, modules and signatures in the symbol dump file for the given compilation target.
- Online location: https://prog8.readthedocs.io/en/latest/libraries.html#low-fi-variable-and-subroutine-definitions-in-all-available-library-modules they are linked there 1 for each compilation target
- Structure: builtin functions, then module sections with variables/constants (`type name`) and subroutines (`name (params) -> returntype`)
- Text output: `textio` module (`txt.print`, `txt.chrout`, `txt.print_b`/`_ub`/`_w`/`_uw`/`_l`/`_bool`, `txt.print_f` for floats, `txt.spc()`, `txt.nl()`)
- Math: `math` module — integer trig (`sin8`, `cos8`) via fast LUTs; `math.rnd()` for random numbers
- String conversion: `conv` module (`str_uword`, `str2word`, etc.) — for printing numbers use txt routines instead
- Char operations: `strings` module (`isdigit`, `isxdigit`, `isupper`, `islower`, `isletter`, `isspace`, `isprint`) — use these instead of manual ASCII/PETSCII arithmetic
- String functions return useful lengths — capture them: `len = strings.copy(dest, src)`, `len = strings.append(buf, text)`, `len = strings.upper(mystr)`
## Interrupts and Hardware
## Syntax & Formatting
- Hex: `$FF` (not `0xFF`); Binary: `%1010` (not `0b1010`). Underscores for readability: `25_000_000`
- 4-digit hex `$0000` = uword. No type suffixes (no `0L`). Cast: `expr as type`
- Augmented assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
- **`;` starts a comment to end of line** — NOT a statement separator. One statement per line
- **No `elif`**: use nested `else { if ... }`
- Type casting: `expression as type` (e.g., `bytevar as word`). `as` has very low precedence (lower than arithmetic)
- **No automatic type widening**: `byte*byte=byte` (overflow possible!). Cast explicitly
- **No block scope**: `for`/`if` don't introduce scope. Only subroutines do. Variables in blocks are hoisted to subroutine level
- **No bare `{ }` blocks** like C/Java
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
- Enums: `Enum::Value` syntax (double colon), declared inside a block. Use enums for related values, `const` for standalone
- Avoid `globals.XXXX` — move constants closer to where they're used
- Member access through pointers: use `.` for both direct and pointer access. The compiler infers the type. For complex assignment targets, `^^` may be needed: `ptr^^.field = value`
- Qualified names: must use full path from top level (e.g., `cx16.r0`, not relative)
- **No block scope** — variables in `if`/`for`/`repeat` blocks are hoisted to subroutine level
- **No reassigning strings** after declaration — use `strings.copy()`/`strings.ncopy()`
- **Pre-allocate string buffers** — `""` allocates nothing
- Keep IRQ handlers short. Set a flag and do substantial work in the main loop.
- Virtual registers `R0-R15` are not preserved across IRQ handlers. Avoid them
or save and restore them with the library routines.
- CX16 IRQ handlers that touch VERA registers must save and restore VERA
context.
- Use only symbolic scratch names such as `P8ZP_SCRATCH_W1` and
`cx16.r0`; never hardcode zeropage addresses.
## Verification
- Use the `virtual` target for behavioral tests when possible:
`prog8c -target virtual -emu program.p8`.
- Use `-check` for syntax and semantic checks without output generation.
- Use `-noopt` to determine whether a failure is optimizer-related.
- For IR execution, use `-vmtrace` when control flow needs inspection.
- Do not modify a correct test program to work around a compiler crash. Reduce
the case and fix the compiler instead.
- When running `gradle` tests, grep for `SUCCESSFUL` to check if all tests
passed, and `FAILED` to check for failures. Gradle does not print individual
`PASSED` lines by default.
+2
View File
@@ -13,5 +13,7 @@ dist/
build/
out/
output/
gradle/
.gradle/
.kotlin/
.git/
+25 -12
View File
@@ -10,32 +10,45 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: build and install recent 64tass
- name: install 64tass
run: |
sudo apt-get install -y make build-essential
git clone --depth=1 https://github.com/irmen/64tass
cd 64tass
make -j4
sudo make install
sudo apt update
sudo apt install 64tass
64tass --version
- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
- name: Build and test with Gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Compile
run: ./gradlew compileKotlin compileTestKotlin --console=plain
- name: Test
run: ./gradlew test --continue --max-workers=3 -PtestConcurrency=2 -PtestMaxHeap=8g --console=plain
- name: Package
run: |
./gradlew build shadowJar --no-daemon
./gradlew shadowJar --console=plain
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
- name: Create compiler shadowJar artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: prog8-compiler-jar-zipped
path: |
compiler/build/libs/*-all.jar
compiler/build/libs/hash.txt
- name: Upload test results
if: always()
uses: actions/upload-artifact@v6
with:
name: test-results
path: compiler/build/test-results/test/
+4 -1
View File
@@ -5,6 +5,7 @@
.idea/shelf/
.junie/
.junie/memory/
.serena
build/
dist/
output/
@@ -14,8 +15,11 @@ out-old/
.*cache/
*.directory
*.prg
*.elf
*.o
*.bin
*.p8ir
*.bankedcalls
*.labels.txt
*.vm.txt
*.vice-mon-list
@@ -38,7 +42,6 @@ parsetab.py
.pytest_cache/
.attach_pid*
compiler/lib/
.gradle
sd*.img
*.d64
+12 -2
View File
@@ -2,6 +2,16 @@
<project version="4">
<component name="CompilerConfiguration">
<option name="BUILD_PROCESS_HEAP_SIZE" value="3000" />
<bytecodeTargetLevel target="11" />
<bytecodeTargetLevel target="11">
<module name="prog8.codeGenExperimental" target="17" />
<module name="prog8.codeGenExperimental.main" target="17" />
<module name="prog8.codeGenExperimental.test" target="17" />
<module name="prog8.codeGenM68k" target="17" />
<module name="prog8.codeGenM68k.main" target="17" />
<module name="prog8.codeGenM68k.test" target="17" />
<module name="prog8.codeGenNew6502" target="17" />
<module name="prog8.codeGenNew6502.main" target="17" />
<module name="prog8.codeGenNew6502.test" target="17" />
</bytecodeTargetLevel>
</component>
</project>
</project>
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/codeCore" />
<option value="$PROJECT_DIR$/codeGenCpu6502" />
<option value="$PROJECT_DIR$/codeGenIntermediate" />
<option value="$PROJECT_DIR$/codeGenM68k" />
<option value="$PROJECT_DIR$/codeGenNew6502" />
<option value="$PROJECT_DIR$/codeOptimizers" />
<option value="$PROJECT_DIR$/compiler" />
<option value="$PROJECT_DIR$/compilerAst" />
<option value="$PROJECT_DIR$/intermediate" />
<option value="$PROJECT_DIR$/languageServer" />
<option value="$PROJECT_DIR$/parser" />
<option value="$PROJECT_DIR$/simpleAst" />
<option value="$PROJECT_DIR$/virtualmachine" />
</set>
</option>
<option name="myGradleHome" value="/usr/share/java/gradle" />
</GradleProjectSettings>
</option>
</component>
</project>
+5 -4
View File
@@ -7,13 +7,14 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="2.3" />
<option name="languageVersion" value="2.3" />
<option name="apiVersion" value="2.4" />
<option name="languageVersion" value="2.4" />
</component>
<component name="KotlinCompilerSettings">
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.3.20" />
<option name="externalSystemId" value="Gradle" />
<option name="version" value="2.4.20" />
</component>
</project>
</project>
-23
View File
@@ -1,23 +0,0 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.20" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20-sources.jar!/" />
</SOURCES>
</library>
</component>
-18
View File
@@ -1,18 +0,0 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.13.2">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-13
View File
@@ -1,13 +0,0 @@
<component name="libraryTable">
<library name="eclipse.lsp4j" type="repository">
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:1.0.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/1.0.0/org.eclipse.lsp4j-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/1.0.0/org.eclipse.lsp4j.jsonrpc-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.14.0/gson-2.14.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.48.0/error_prone_annotations-2.48.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-14
View File
@@ -1,14 +0,0 @@
<component name="libraryTable">
<library name="github.irmen.ksim65" type="repository">
<properties maven-id="com.github.irmen:ksim65:v2.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.0/ksim65-v2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-23
View File
@@ -1,23 +0,0 @@
<component name="libraryTable">
<library name="io.kotest.framework.datatest" type="repository">
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-41
View File
@@ -1,41 +0,0 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.8.0/kotlinx-coroutines-debug-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.8.2/junit-platform-suite-api-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-10
View File
@@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-20
View File
@@ -1,20 +0,0 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+1
View File
@@ -20,6 +20,7 @@
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
-14
View File
@@ -3,22 +3,8 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" filepath="$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
</modules>
</component>
</project>
Generated
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>
-31
View File
@@ -1,31 +0,0 @@
{
"context": {
"fileName": "AGENTS.md"
},
"$version": 3,
"permissions": {
"allow": [
"Bash(prog8c *)",
"Bash(gradle *)",
"Bash(mkdir *)",
"Bash(ls *)",
"Bash(cat *)",
"Bash(find *)",
"Bash(sed *)",
"Bash(grep *)",
"Bash(xargs *)",
"Bash(wc *)",
"Bash(diff *)",
"Bash(paste *)",
"Bash(echo *)",
"Bash(cp *)",
"Bash(time)",
"Bash(kotlinc *)",
"Bash(python3 *)",
"Bash(jar *)",
"Bash(od *)",
"Bash(xxd *)",
"Bash(gcc *)"
]
}
}
+38 -16
View File
@@ -30,11 +30,12 @@
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
- The standard library is mostly written in Prog8 and assembly code, and can be found in the "compiler" module, in the 'res/prog8lib' directory.
- **Library Reference**: For a quick overview of all available modules, routines, and their signatures, **consult the skeleton files (symboldumps)** in `docs/source/_static/symboldumps/`. They are named `skeletons-<target>.txt`.
- Kotlin version 2.3 is used for the compiler implementation.
- Kotlin version 2.4 is used for the compiler implementation.
- Java 17 is used as Java runtime version.
- ANTLR4 version 4.13 is used for the parser implementation.
- Dependent library versions can be found in 'build.gradle.kts' and in the IntelliJ IDEA configuration files in .idea/libraries
- The compiler main entrypoint is in the "compiler" module, in src/prog8/CompilerMain.kt
- **Codebase knowledge graph**: For a structured overview of modules, their dependencies, key entities, the end-to-end compilation pipeline, the standard library layout, and the test structure, consult `docs/CODEBASE-KNOWLEDGE-GRAPH.md`. Read it when you need to understand how the compiler fits together before diving into specific files.
## Compilation Flow (High-Level)
```
@@ -53,7 +54,8 @@ Source → parseMainModule() → processAst() → optimizeAst() → postprocessA
### Target Differences
- **CPU instruction set differences**: Only the CommanderX16 target (cx16) can use 65C02 instructions such as STZ. The other targets (C64, C128, PET32) can only use original 6502 instructions.
- When writing or understanding assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
- When writing or understanding 6502/65C02 assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
- When writing or understanding M68K assembly code (amiga500, qemu68k targets), load the `m68k-coder` skill for vasm mot syntax and conventions.
## DEBUGGING TIP: Use `-noopt` to isolate problems
@@ -127,6 +129,12 @@ Steps when encountering a compiler crash:
### Code Style Guidelines
**Minimal comments when making changes**: When modifying existing code, add only essential comments that explain *why* a change was made or document non-obvious behavior. **Do not add verbose comments** that restate what the code does; let the code speak for itself. Existing extensive comments should be preserved, but new changes should have minimal commentary.
**Kotlin string `$` handling**: The `$` character is special in Kotlin string literals (string interpolation). If you need a literal `$` character, use Kotlin's `$$"..."` interpolation prefix instead of backslash escaping (`\$`) or `${'$'}`. The `$$"..."` syntax is a fairly recent Kotlin feature that avoids nasty escaping. For example:
```kotlin
val bill = $$"The value is $3.99"
```
## Prog8 language information
When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir` (Prog8 intermediate representation) files, **load the `prog8-coder` skill** — it contains the full language reference, syntax rules, and standard library guidance.
@@ -153,18 +161,6 @@ When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir`
- **git log/history queries can be useful** for understanding when/why a feature was added or tracking down when a bug was introduced, but for locating code use grep_search or glob instead.
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
## Junie Agent Commitments
These instructions apply specifically to the Junie agent:
* **Code Discovery and Navigation**: Use the `get_file_structure` tool to understand the API and structure of files before reading or editing them.
* **Symbol-Aware Searching**: Use the `search_project` tool as the primary method for finding symbol definitions (classes, methods, variables) across the project.
* **Project-Wide Refactoring**: Exclusively use the `rename_element` tool for any symbol renames to ensure all references, imports, and documentation are updated correctly.
* **Structured Test Execution**: Use the `run_test` tool for rapid, isolated verification of Kotlin logic changes.
* **Contextual Exploration**: Use the `open` tool with specific `line_number` parameters to navigate directly to relevant code sections.
* **Gradle Console Output**: Always use the `--console=plain` flag with `gradle` commands. Fancy console output (like progress bars) will mess up the coding agent's text interface.
**Note on Terminal usage**: Junie uses `gradle` and terminal commands primarily for project lifecycle tasks (e.g., `gradle installdist` to re-embed standard library resources), manual compiler verification with `prog8c`, and full regression testing before submission. When using `gradle`, always append `--console=plain`.
# Dev environment tips
## Development Workflows
@@ -202,9 +198,10 @@ These instructions apply specifically to the Junie agent:
- This compiles AND installs, but still skips running tests (faster than `gradle build`)
### When to run full build with tests
- **Before committing changes**: Always run `gradle build` to ensure all tests pass
- **Before final verification**: Always run `gradle build` to ensure all tests pass
- **After major refactoring**: Run `gradle build` to catch regressions
- **When debugging test failures**: Use `gradle test --tests "*TestName*"` for specific tests
- **When working on a standalone code gen module** (one that reads `.p8ir` files): Run `gradle :<module>:test --console=plain` instead of full `gradle build`. Compiler unit tests are irrelevant for IR-level changes. Do run full `gradle build` before final verification.
### Build command summary
@@ -225,6 +222,7 @@ These instructions apply specifically to the Junie agent:
- **the `-printast1` switch prints out the internal Compiler AST** after parsing and semantic analysis.
- **the `-printast2` switch prints out the optimized Simple AST** just before it goes to the code generator. This is useful for debugging optimizer issues.
- **the `-out outdir` switch sets an alternative output directory** for compiled files (.prg, .asm, .list, etc.). **By default, output files are written to the same directory as the source file**.
- **Other useful flags**: `-quiet` (suppress messages), `-warnimplicitcasts` (warn on implicit type widening), `-daemon` (keep a background compiler process alive — must be passed on every invocation)
### Compilation Output Files
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
@@ -238,10 +236,15 @@ These instructions apply specifically to the Junie agent:
- `prog8c -target targetname -emu input.p8` - Compile and execute a prog8 file in the emulator for the given target (cx16, c64, pet32, c128, virtual)
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
### Library Tools
- **Library search**: `prog8c -libsearch <regex>` — search for a regex pattern in the embedded stdlib files. Extremely useful to quickly find library routines, variables, or signatures (e.g., `prog8c -libsearch "txt\."` lists all textio routines)
- **Library dump**: `prog8c -libdump <dir>` — extract all embedded library source files into a directory for direct inspection
## Testing and Verification
- Never assume your changes simply work, always test!
- If the project does not have any testing tools, scripts, MCP tools, skills, etc. available for testing, ask the user whether testing should be skipped.
- in prog8 code being tested, use '@shared' variables to avoid const-folding
### Automated Tests (gradle)
Always use the `--console=plain` flag with these commands to avoid messing up the agent's text interface.
@@ -281,9 +284,28 @@ gradle :compiler:compileTestKotlin --info 2>&1 | grep "^e:"
- Unit tests use KoTest (FunSpec style)
- Tests are in the "compiler" module's `test` directory (and some other modules)
- Test config is in root `build.gradle.kts`; tests run in parallel
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit`
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit` - the latter ONLY when NOT writing for the amiga500 or qemu68k targets
- When a test fails, the output shows "There were failing tests. See the report at:" - **read that HTML report**
### Compiler Unit Test Snippets
Prog8 code snippets embedded in the compiler's own Kotlin unit tests (e.g., in `TestAstChecks.kt`, `TestOptimization.kt`) follow different rules from standalone programs:
- **Keep self-contained** — avoid `%import` directives (test setup may not have library search path configured)
- **Use `cx16` target** (not `virtual`, unless specifically testing the VM)
- **Set `writeAssembly=false`** if the test only checks generated AST (much faster)
- **Set `optimize=false`** by default for these snippets
- **Do NOT use** the `%encoding iso` / `txt.iso()` / `sys.poweroff_system()` pattern — that's only for real CX16 emulator runs
### Testing Assembly Output
When testing generated assembly in Kotlin tests:
- **NEVER dump raw assembly in string assertions** (e.g., `asm shouldContain "jsr label"`). On failure, this dumps the ENTIRE assembly listing into test logs.
- **Instead, parse into lines and check specific properties:**
```kotlin
val lines = asm.lines().map { it.trim() }
lines.any { it == "lda #42" } shouldBe true
lines.count { it.startsWith("jsr") } shouldBe 0
```
- Prefer `lines.any { ... }` for existence checks and `lines.count { ... } shouldBe 0` for absence checks.
### Manual Verification & Emulators
**Testing tip**: When writing and testing Prog8 programs, **use the `virtual` target** (e.g., `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir`). This is the preferred way to test because the virtual target can easily write output to stdout, making it simple to verify program behavior and check results.
+2 -2
View File
@@ -12,9 +12,9 @@ endif
all:
$(GRADLE) installdist installshadowdist
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
test:
$(GRADLE) build
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
+16 -7
View File
@@ -4,21 +4,24 @@ PayPal: [![PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)]
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/)
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
============================================================================
Prog8 - Structured Programming Language for 6502/65c02 (8-bit) and 68000 (32-bit) microprocessors
=================================================================================================
*Written by Irmen de Jong (irmen@razorvine.net)*
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
as used in many home computers from that era. It is a medium to low level programming language,
as used in many home computers from that era. It also supports the 32-bit Motorola 68000 CPU, for which the
Commodore Amiga is the intended machine. It is a medium to low level programming language,
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
**Want to buy me a coffee or a pizza perhaps?**
This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can.
This project was created over the last couple of years by dedicating a huge amount of my free time to it, to make it the best I possibly can.
If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong)
[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSource)
I am using JetBrains products to develop the Prog8 compiler.
Documentation
-------------
@@ -63,6 +66,7 @@ What does Prog8 provide?
- programs run very fast because it's compiled to native machine code
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
- modularity, symbol scoping, subroutines. No need for forward declarations.
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
- various data types other than just bytes (16-bit words, long integers, floats, strings)
- Structs and typed pointers
- 2D arrays (`matrix[row][col]` syntax)
@@ -91,7 +95,7 @@ What does Prog8 provide?
*Rapid edit-compile-run-debug cycle:*
- use a modern PC to do the work on, use nice editors and enjoy quick compilation times
- can automatically run the program in the Vice emulator after succesful compilation
- can automatically run the program in the Vice emulator after successful compilation
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
@@ -101,17 +105,22 @@ What does Prog8 provide?
- "c64": Commodore-64 (6502 like CPU)
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
- "pet32": Commodore PET (limited support)
- "amiga500": Commodore Amiga 500 (16/32-bit 68000 CPU; experimental)
- "amiga1200": Commodore Amiga 1200 (32-bit 68020 CPU with optional FPU; experimental)
- "qemu68k": Qemu M68k simulator (experimental)
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64 OS, Foenix F256, ...
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
The m68k targets (amiga500, amiga1200, qemu68k) and the optional `-newcodegen` 6502 code generator are experimental and not yet suitable for production code.
Additional required tools
-------------------------
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
For other platforms it is very easy to compile it yourself (make ; make install).
If you're on Windows, download a recent .exe from the official repository's file section <https://sourceforge.net/projects/tass64/files/binaries/>.
For other systems (Mac, Linux) it is very easy to get it via your package manager or to compile it yourself.
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
+13 -12
View File
@@ -41,7 +41,7 @@ rotate3d {
for i in 0 to len(matrix_math.xcoor)-1 {
rz = matrix_math.rotatedz[i]
if rz >= 10 {
persp = 600 + rz/64
persp = 600 + (rz>>5)
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
;; txt.setcc(sx as ubyte, sy as ubyte, 46, 7)
@@ -51,7 +51,7 @@ rotate3d {
for i in 0 to len(matrix_math.xcoor)-1 {
rz = matrix_math.rotatedz[i]
if rz < 10 {
persp = 600 + rz/64
persp = 600 + (rz>>5)
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
;; txt.setcc(sx as ubyte, sy as ubyte, 81, 7)
@@ -84,18 +84,19 @@ matrix_math {
word wcosc = math.cos8(az)
word wsinc = math.sin8(az)
word wcosa_sinb = wcosa*wsinb / 128
word wsina_sinb = wsina*wsinb / 128
; instead of (slow) /128, we simply shift 7 bits (and take the roundoff error)
word wcosa_sinb = wcosa*wsinb >> 7
word wsina_sinb = wsina*wsinb >> 7
word Axx = wcosa*wcosb / 128
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) / 128
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) / 128
word Ayx = wsina*wcosb / 128
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) / 128
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) / 128
word Axx = wcosa*wcosb >> 7
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) >> 7
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) >> 7
word Ayx = wsina*wcosb >> 7
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) >> 7
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) >> 7
word Azx = -wsinb
word Azy = wcosb*wsinc / 128
word Azz = wcosb*wcosc / 128
word Azy = wcosb*wsinc >> 7
word Azz = wcosb*wcosc >> 7
ubyte @zp i
for i in 0 to len(xcoor)-1 {
+3 -3
View File
@@ -220,10 +220,10 @@ btree {
arena {
; extremely trivial arena allocator (that never frees)
uword buffer = memory("arena", 2000, 0)
uword next = buffer
pointer buffer = memory("arena", 2000, 0)
pointer next = buffer
sub alloc(ubyte size) -> uword {
sub alloc(ubyte size) -> pointer {
defer next += size
return next
}
+1 -2
View File
@@ -61,8 +61,7 @@ life {
math.rndseed(12345,9999)
uword offset = STRIDE+1
ubyte x
ubyte y
for y in 0 to HEIGHT-1 {
repeat HEIGHT {
for x in 0 to WIDTH-1 {
active_world[offset+x] = math.rnd() & 1
}
+10 -6
View File
@@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm") version "2.3.20"
kotlin("jvm") version "2.4.20"
}
allprojects {
@@ -52,8 +52,8 @@ subprojects {
// Common test dependencies via Kotest BOM (Bill of Materials)
// This manages all Kotest module versions centrally
dependencies {
testImplementation(platform("io.kotest:kotest-bom:5.9.1"))
// implementation("com.github.irmen:ksim65:v2.0")
testImplementation(platform("io.kotest:kotest-bom:6.2.4"))
// implementation("org.codeberg.irmen:ksim65:v2.2")
}
// Common sourceSets configuration
@@ -72,16 +72,20 @@ subprojects {
// Enable JUnit 5 (required for Kotest)
useJUnitPlatform()
val testConcurrency = project.findProperty("testConcurrency")?.toString()?.toIntOrNull()
?: (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
project.findProperty("testMaxHeap")?.toString()?.let { maxHeapSize = it }
// Enable concurrent test execution for Kotest
// Set parallelism to number of CPU cores
jvmArgs("-Dkotest.framework.parallelism=${Runtime.getRuntime().availableProcessors()}")
// Allow CI to override parallelism for memory-constrained runners
jvmArgs("-Dkotest.framework.parallelism=$testConcurrency")
// Disable Kotest autoscan warning
jvmArgs("-Dkotest.framework.classpath.scanning.autoscan.disable=true")
// Enable Gradle's parallel test execution (runs multiple test classes concurrently)
// Use 50% of available processors to avoid over-subscription
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
maxParallelForks = testConcurrency
// Allow filtering tests via -PtestFilter="*TestName*" from command line
// Example: gradle test -PtestFilter="*TestLookup*"
-1
View File
@@ -8,5 +8,4 @@ dependencies {
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
testImplementation("io.kotest:kotest-runner-junit5")
testImplementation("io.kotest:kotest-framework-datatest")
}
-17
View File
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
@@ -1,7 +1,6 @@
package prog8.codegen.cpu6502
package prog8.code.assembly
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.IAssemblyProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.ICompilationTarget
import prog8.code.core.IErrorReporter
@@ -12,10 +11,10 @@ import prog8.code.target.PETTarget
import java.nio.file.Path
internal class AssemblyProgram(
override val name: String,
outputDir: Path,
private val compTarget: ICompilationTarget) : IAssemblyProgram {
class AssemblyProgram6502(
override val name: String,
outputDir: Path,
private val compTarget: ICompilationTarget) : IAssemblyProgram {
override val irInstructionCount: Int = 0
override val irChunkCount: Int = 0
@@ -133,12 +132,31 @@ internal class AssemblyProgram(
assemblerCommand = addRemainingOptions(command, binFile, assemblyFile)
}
else -> error("Unsupported output type: ${compTarget.defaultOutputType}")
}
val proc = ProcessBuilder(assemblerCommand)
if(!options.quiet)
proc.inheritIO()
val process = proc.start()
.redirectErrorStream(true)
if (options.quiet) {
proc.redirectOutput(ProcessBuilder.Redirect.DISCARD)
}
val process = try {
proc.start()
} catch (e: Exception) {
System.err.println("daemon: assembler failed to start: ${e.message}")
return false
}
if (!options.quiet) {
process.inputStream.bufferedReader().use { reader ->
reader.forEachLine {
println(it)
}
}
}
val result = process.waitFor()
if (result == 0) {
removeGeneratedLabelsFromMonlist()
@@ -174,3 +192,4 @@ internal class AssemblyProgram(
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
}
}
@@ -0,0 +1,12 @@
package prog8.code.assembly
import prog8.code.core.CompilationOptions
import prog8.code.core.IErrorReporter
interface IAssemblyProgram {
val name: String
val irInstructionCount: Int
val irChunkCount: Int
val irRegisterCount: Int
fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean
}
@@ -24,7 +24,7 @@ class CallConvention(val params: List<ParamConvention>, val returns: ReturnConve
class FParam(val name: String, vararg val possibleDatatypes: BaseDataType)
private val IterableDatatypes = arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
private val IterableDatatypes = arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
private val IntegerDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
private val NumericDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
@@ -33,7 +33,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
val returnTypes: Array<BaseDataType>,
vararg val parameters: FParam) {
fun callConvention(actualParamTypes: List<BaseDataType>): CallConvention {
fun callConventionFor6502(actualParamTypes: List<BaseDataType>): CallConvention {
val returns: ReturnConvention
if(returnTypes.isEmpty())
returns = ReturnConvention(null, null)
@@ -41,7 +41,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
val returnType = returnTypes[0]
returns = when (returnType) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> ReturnConvention(returnType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(returnType, RegisterOrPair.AY)
@@ -49,7 +49,7 @@ class FSignature(val pure: Boolean, // does it have side effects?
// return type depends on arg type
when (val paramType = actualParamTypes.first()) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> ReturnConvention(paramType, RegisterOrPair.AY)
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
@@ -109,9 +109,9 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"prog8_lib_square_word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
"prog8_lib_square_long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
"prog8_lib_structalloc" to FSignature(true, arrayOf(BaseDataType.UWORD)),
"prog8_lib_copylong" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"abs" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
"prog8_lib_copylong" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("pointer2", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("pointer2", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
"abs" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD)),
"abs__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
@@ -120,7 +120,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"sizeof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"offsetof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("field", BaseDataType.UBYTE)),
"sgn" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE)),
"sqrt__uword" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD)),
"sqrt__long" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
@@ -140,50 +140,55 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"mkword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong2" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
"clamp" to FSignature(true, emptyArray(), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
"clamp__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
"clamp__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
"clamp__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
"min" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"min__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"min__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"min__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"max" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"max__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"max__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"max__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"peek" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD)),
"peekbool" to FSignature(true, arrayOf(BaseDataType.BOOL), FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
"peekl" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("address", BaseDataType.UWORD)),
"peekf" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("address", BaseDataType.UWORD)),
"poke" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
"pokebool" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokebowl" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokew" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
"pokel" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"pokef" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
"pokemon" to FSignature(false, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"peek" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
"peekbool" to FSignature(true, arrayOf(BaseDataType.BOOL), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
"peekw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
"peekl" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
"peekf" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
"peekp" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("address", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
"poke" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
"pokebool" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.BOOL)),
"pokebowl" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.BOOL)),
"pokew" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
"pokel" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.LONG)),
"pokef" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.FLOAT)),
"pokep" to FSignature(false, emptyArray(), FParam("address", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.POINTER)),
"pokemon" to FSignature(false, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UBYTE)),
"rsave" to FSignature(false, emptyArray()),
"rrestore" to FSignature(false, emptyArray()),
"memory" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
"call" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
"memory" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
"memory__ref" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("name", BaseDataType.STR)),
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.POINTER), FParam("arg", BaseDataType.UWORD)),
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.POINTER), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
"call" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.POINTER)),
"push" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)),
"pushw" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER)),
"pushl" to FSignature(false, emptyArray(), FParam("value", BaseDataType.LONG)),
"pushf" to FSignature(false, emptyArray(), FParam("value", BaseDataType.FLOAT)),
"pushp" to FSignature(false, emptyArray(), FParam("value", BaseDataType.POINTER)),
"pop" to FSignature(false, arrayOf(BaseDataType.UBYTE)),
"popw" to FSignature(false, arrayOf(BaseDataType.UWORD)),
"popl" to FSignature(false, arrayOf(BaseDataType.LONG)),
"popf" to FSignature(false, arrayOf(BaseDataType.FLOAT)),
"popp" to FSignature(false, arrayOf(BaseDataType.POINTER)),
)
val InplaceModifyingBuiltinFunctions = setOf(
@@ -4,6 +4,7 @@ import java.nio.file.Path
import kotlin.io.path.Path
@Suppress("unused")
class CompilationOptions(val output: OutputType,
val launcher: CbmPrgLauncherType,
val zeropage: ZeropageType,
@@ -17,6 +18,7 @@ class CompilationOptions(val output: OutputType,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
var memtopAddress: UInt,
var varsAddress: UInt? = null,
var warnSymbolShadowing: Boolean = false,
var warnImplicitTypeCast: Boolean = false,
var optimize: Boolean = false,
@@ -25,17 +27,16 @@ class CompilationOptions(val output: OutputType,
var includeSourcelines: Boolean = false,
var dumpVariables: Boolean = false,
var dumpSymbols: Boolean = false,
var experimentalCodegen: Boolean = false,
var newCodegen: Boolean = false,
var varsHighBank: Int? = null,
var varsGolden: Boolean = false,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var breakpointCpuInstruction: String? = null,
var ignoreFootguns: Boolean = false,
var outputDir: Path = Path(""),
var quiet: Boolean = false,
var profilingInstrumentation: Boolean = false,
var symbolDefs: Map<String, String> = emptyMap()
var symbolDefs: Map<String, String> = emptyMap(),
var privateSymbols: Boolean = false
) {
init {
compTarget.initializeMemoryAreas(this)
@@ -58,6 +59,7 @@ class CompilationOptions(val output: OutputType,
private var compilerVersion: String = "unknown"
private var loadAddress: UInt = compTarget.PROGRAM_LOAD_ADDRESS
private var memtopAddress: UInt = compTarget.PROGRAM_MEMTOP_ADDRESS
private var varsAddress: UInt? = null
private var warnSymbolShadowing: Boolean = false
private var warnImplicitTypeCast: Boolean = false
private var optimize: Boolean = false
@@ -66,16 +68,14 @@ class CompilationOptions(val output: OutputType,
private var includeSourcelines: Boolean = false
private var dumpVariables: Boolean = false
private var dumpSymbols: Boolean = false
private var experimentalCodegen: Boolean = false
private var varsHighBank: Int? = null
private var varsGolden: Boolean = false
private var slabsHighBank: Int? = null
private var slabsGolden: Boolean = false
private var breakpointCpuInstruction: String? = null
private var ignoreFootguns: Boolean = false
private var outputDir: Path = Path("")
private var quiet: Boolean = false
private var profilingInstrumentation: Boolean = false
private var newCodegen: Boolean = false
private var symbolDefs: Map<String, String> = emptyMap()
fun output(output: OutputType) = apply { this.output = output }
@@ -89,6 +89,7 @@ class CompilationOptions(val output: OutputType,
fun compilerVersion(compilerVersion: String) = apply { this.compilerVersion = compilerVersion }
fun loadAddress(loadAddress: UInt) = apply { this.loadAddress = loadAddress }
fun memtopAddress(memtopAddress: UInt) = apply { this.memtopAddress = memtopAddress }
fun varsAddress(varsAddress: UInt?) = apply { this.varsAddress = varsAddress }
fun warnSymbolShadowing(warnSymbolShadowing: Boolean) = apply { this.warnSymbolShadowing = warnSymbolShadowing }
fun warnImplicitTypeCast(warnImplicitTypeCast: Boolean) = apply { this.warnImplicitTypeCast = warnImplicitTypeCast }
fun optimize(optimize: Boolean) = apply { this.optimize = optimize }
@@ -97,24 +98,22 @@ class CompilationOptions(val output: OutputType,
fun includeSourcelines(includeSourcelines: Boolean) = apply { this.includeSourcelines = includeSourcelines }
fun dumpVariables(dumpVariables: Boolean) = apply { this.dumpVariables = dumpVariables }
fun dumpSymbols(dumpSymbols: Boolean) = apply { this.dumpSymbols = dumpSymbols }
fun experimentalCodegen(experimentalCodegen: Boolean) = apply { this.experimentalCodegen = experimentalCodegen }
fun varsHighBank(varsHighBank: Int?) = apply { this.varsHighBank = varsHighBank }
fun varsGolden(varsGolden: Boolean) = apply { this.varsGolden = varsGolden }
fun slabsHighBank(slabsHighBank: Int?) = apply { this.slabsHighBank = slabsHighBank }
fun slabsGolden(slabsGolden: Boolean) = apply { this.slabsGolden = slabsGolden }
fun breakpointCpuInstruction(breakpointCpuInstruction: String?) = apply { this.breakpointCpuInstruction = breakpointCpuInstruction }
fun ignoreFootguns(ignoreFootguns: Boolean) = apply { this.ignoreFootguns = ignoreFootguns }
fun outputDir(outputDir: Path) = apply { this.outputDir = outputDir }
fun quiet(quiet: Boolean) = apply { this.quiet = quiet }
fun profilingInstrumentation(profilingInstrumentation: Boolean) = apply { this.profilingInstrumentation = profilingInstrumentation }
fun symbolDefs(symbolDefs: Map<String, String>) = apply { this.symbolDefs = symbolDefs }
fun newCodegen(newCodegen: Boolean) = apply { this.newCodegen = newCodegen }
fun build(): CompilationOptions {
return CompilationOptions(
output, launcher, zeropage, zpReserved, zpAllowed, floats, noSysInit, romable, compTarget, compilerVersion,
loadAddress, memtopAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
includeSourcelines, dumpVariables, dumpSymbols, experimentalCodegen, varsHighBank, varsGolden,
slabsHighBank, slabsGolden, breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
loadAddress, memtopAddress, varsAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
includeSourcelines, dumpVariables, dumpSymbols, newCodegen, varsHighBank, varsGolden,
breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
profilingInstrumentation, symbolDefs
)
}
@@ -98,3 +98,24 @@ fun String.unescape(): String {
}
return result.joinToString("")
}
// === Symbol name prefixing ===
// These are used by code generators to add p8b_/p8s_/p8v_/etc. prefixes to symbol names.
// The prefix character depends on the symbol type (block='b', sub='s', variable='v', etc.)
// Scoped names (containing dots) have each part prefixed according to its role.
fun prefixScopedName(name: String, type: Char): String {
val generatedPrefix = "p8_label_gen_"
if('.' !in name) {
if(name.startsWith(generatedPrefix))
return name
return "p8${type}_$name"
}
val parts = name.split('.')
val firstPrefixed = "p8b_${parts[0]}"
val lastPart = parts.last()
val lastPrefixed = if(lastPart.startsWith(generatedPrefix)) lastPart else "p8${type}_$lastPart"
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
return prefixed.joinToString(".")
}
+126 -41
View File
@@ -20,7 +20,7 @@ enum class BaseDataType {
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
POINTER, // typed pointer, subtype is whatever type is pointed to
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
ARRAY_POINTER, // array of pointers (uwords or longs), subtype is whatever type each element points to
UNDEFINED;
@@ -30,22 +30,28 @@ enum class BaseDataType {
this.isByteOrBool -> false
this.isWord -> other.isByteOrBool
this == LONG -> other.isByteOrBool || other.isWord
this == STR && other == UWORD || this == UWORD && other == STR -> false
this == STR && (other == UWORD || other == LONG) || this == UWORD && other == STR -> false
this.isArray && other.isArray -> false
this.isArray -> other != FLOAT
this == STR -> other != FLOAT
this.isArray -> other.isByteOrBool
this == STR -> other.isByteOrBool
this.isPointer -> other.isByteOrBool
else -> true
}
fun equalsSize(other: BaseDataType) =
fun equalsSize(other: BaseDataType): Boolean = equalsSize(other, null)
fun equalsSize(other: BaseDataType, memsizer: IMemSizer?): Boolean =
when {
this == other -> true
this.isArray && other.isArray -> true
this.isByteOrBool -> other.isByteOrBool
this.isWord -> other.isWord || other.isPointer
this.isPointer -> other.isWord
this == STR && other== UWORD || this== UWORD && other== STR -> true
this.isWord -> other.isWord || (other.isPointer && (memsizer == null || memsizer.POINTER_MEM_SIZE == 2u))
this.isLong -> other.isLong || (other.isPointer && memsizer?.POINTER_MEM_SIZE != null && memsizer.POINTER_MEM_SIZE != 2u)
this.isPointer -> when(memsizer?.POINTER_MEM_SIZE) {
null -> other.isWord || other.isPointer
2u -> other.isWord || other.isPointer
else -> other.isLong || other.isPointer
}
this == STR && (other == UWORD || other == LONG) || (this == UWORD || this == LONG) && other == STR -> true
this == STR && other.isArray -> true
this.isArray && other == STR -> true
else -> false
@@ -68,16 +74,27 @@ val BaseDataType.isSignedWord get() = this == BaseDataType.WORD
val BaseDataType.isLong get() = this == BaseDataType.LONG
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
val BaseDataType.isInteger get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
val BaseDataType.maxUnsignedValue: Double
get() = when (this) {
BaseDataType.UBYTE -> 255.0
BaseDataType.BYTE -> 127.0
BaseDataType.UWORD -> 65535.0
BaseDataType.WORD -> 32767.0
BaseDataType.LONG -> 2147483647.0
else -> Double.MAX_VALUE
}
val BaseDataType.isIntegerOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
val BaseDataType.isUnsigned get() = !this.isSigned
val BaseDataType.isSignedInteger get() = this.isSigned && this.isInteger
val BaseDataType.isUnsignedInteger get() = this.isUnsigned && this.isInteger
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
@@ -119,8 +136,10 @@ class DataType private constructor(
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
}
base.isArray -> {
require(sub != null && subType==null && subTypeFromAntlr==null)
if(base.isSplitWordArray)
require(sub != null)
// subType and subTypeFromAntlr allowed for arrays with struct element types
// (from parser creating ARRAY(LONG) directly for 32-bit targets, or from VariousCleanups conversion)
if(base == BaseDataType.ARRAY_SPLITW)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
}
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
@@ -136,7 +155,12 @@ class DataType private constructor(
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DataType) return false
return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!))
if (base != other.base || sub != other.sub) return false
val st1 = subType
val st2 = other.subType
if (st1 == st2) return true
if (st1 == null || st2 == null) return false
return st1.sameas(st2)
}
override fun hashCode(): Int = Objects.hash(base, sub, subType)
@@ -171,26 +195,33 @@ class DataType private constructor(
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
BaseDataType.POINTER to DataType(BaseDataType.POINTER, null, null),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
)
fun forDt(dt: BaseDataType): DataType {
if(dt.isStructInstance)
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
return DataType(BaseDataType.STRUCT_INSTANCE, null, null)
return simpletypes.getValue(dt)
}
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
fun arrayFor(elementDt: BaseDataType, memsizer: IMemSizer): DataType {
// wether or not a word-array should be split-words, is determined later
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
else {
if(actualElementDt.isNumericOrBool)
DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid basic element dt $elementDt")
}
// On 32-bit targets (m68k, virtual), str arrays become arrays of LONG (4-byte pointers)
// via pointerBaseType. On 6502 targets they become arrays of UWORD (2-byte pointers).
val actualElementDt = if(elementDt==BaseDataType.STR) {
memsizer.pointerBaseType
} else elementDt
if(actualElementDt.isNumericOrBool)
return DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid basic element dt $elementDt")
}
fun splitWordArrayFor(elementDt: BaseDataType): DataType {
require(elementDt.isWord) { "split word array element type must be word" }
return DataType(BaseDataType.ARRAY_SPLITW, elementDt, null)
}
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
@@ -198,6 +229,11 @@ class DataType private constructor(
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
// array of struct instances: sub is STRUCT_INSTANCE, subType carries the resolved struct
fun arrayOfStructs(structType: ISubType): DataType = DataType(BaseDataType.ARRAY, BaseDataType.STRUCT_INSTANCE, structType)
fun arrayOfStructsFromAntlr(struct: List<String>): DataType =
DataType(BaseDataType.ARRAY, BaseDataType.STRUCT_INSTANCE, null, subTypeFromAntlr = struct)
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
fun pointer(dt: DataType): DataType = if(dt.isBasic)
DataType(BaseDataType.POINTER, dt.base, null)
@@ -214,38 +250,74 @@ class DataType private constructor(
// DataType Methods
// ============================================================================
fun elementToArray(splitwords: Boolean = true): DataType {
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
else arrayFor(base, false)
fun elementToArray(target: ICompilationTarget, splitwords: Boolean = true): DataType {
fun pointerArray(): DataType =
if (subType != null)
arrayOfPointersTo(subType)
else if (subTypeFromAntlr != null)
arrayOfPointersFromAntlrTo(sub, subTypeFromAntlr)
else
arrayOfPointersTo(sub!!)
if(base == BaseDataType.STRUCT_INSTANCE) {
val st = subType
return if(st != null)
arrayOfStructs(st)
else
arrayOfStructsFromAntlr(subTypeFromAntlr!!)
}
if(!target.cpu.is6502) {
return if (base.isPointer)
pointerArray()
else
arrayFor(base, target)
}
return if (splitwords && (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR))
splitWordArrayFor(base)
else if(base.isPointer)
pointerArray()
else
arrayFor(base, target)
}
fun elementType(): DataType =
when {
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
base.isArray || base==BaseDataType.STR -> {
if (sub == BaseDataType.STRUCT_INSTANCE && subType != null)
return DataType(BaseDataType.STRUCT_INSTANCE, null, subType)
val dt = forDt(sub!!)
if (subType != null) DataType(BaseDataType.POINTER, null, subType) else dt
}
else -> throw IllegalArgumentException("not an array")
}
fun typeForAddressOf(msb: Boolean): DataType {
fun typeForUntypedAddressOf(msb: Boolean, mem: IMemSizer): DataType {
val untypedPointerType = if(mem.memorySize(BaseDataType.POINTER)>2) LONG else UWORD
if (isUndefined)
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
return if(msb) pointer(BaseDataType.UBYTE) else untypedPointerType
else {
if (isBasic)
return pointer(base)
if (isString)
return pointer(BaseDataType.UBYTE)
if (isPointer)
return UWORD
return untypedPointerType
if (isArray) {
if (msb || isSplitWordArray)
val isSplit = base == BaseDataType.ARRAY_SPLITW ||
(base == BaseDataType.ARRAY_POINTER && mem.memorySize(BaseDataType.POINTER) <= 2)
if (msb || isSplit)
return pointer(BaseDataType.UBYTE)
val elementDt = elementType()
require(elementDt.isBasic)
return pointer(elementDt)
if (elementDt.isBasic)
return pointer(elementDt)
return untypedPointerType
}
if (subType != null)
return pointer(this)
return UWORD
return untypedPointerType
}
}
@@ -270,6 +342,7 @@ class DataType private constructor(
BaseDataType.UBYTE -> "ubyte[]"
BaseDataType.UWORD -> "uword[]"
BaseDataType.LONG -> "long[]"
BaseDataType.STRUCT_INSTANCE -> if (subType != null) "${subType!!.scopedNameString}[]" else "$subTypeFromAntlr[]"
else -> throw IllegalArgumentException("invalid sub type")
}
}
@@ -334,6 +407,7 @@ class DataType private constructor(
BaseDataType.WORD -> "@nosplit word["
BaseDataType.LONG -> "long["
BaseDataType.FLOAT -> "float["
BaseDataType.STRUCT_INSTANCE -> if (subType != null) "${subType!!.scopedNameString}[" else "$subTypeFromAntlr["
else -> throw IllegalArgumentException("invalid sub type")
}
}
@@ -358,9 +432,9 @@ class DataType private constructor(
BaseDataType.BYTE -> targetType.base in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER)
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD, BaseDataType.LONG) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
BaseDataType.POINTER -> {
when {
@@ -369,13 +443,14 @@ class DataType private constructor(
else -> false
}
}
BaseDataType.ARRAY_POINTER -> targetType.base in setOf(BaseDataType.ARRAY_POINTER, targetType.base == BaseDataType.ARRAY) && targetType.sub == sub
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
BaseDataType.ARRAY_POINTER -> false
BaseDataType.UNDEFINED -> false
}
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
fun equalsSize(other: DataType, memsizer: IMemSizer): Boolean = base.equalsSize(other.base, memsizer)
/**
* Returns the memory size in bytes.
@@ -431,10 +506,20 @@ class DataType private constructor(
val isBool = base == BaseDataType.BOOL
val isFloat = base == BaseDataType.FLOAT
val isLong = base == BaseDataType.LONG
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
val isSplitWordArray = base.isSplitWordArray
val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD
fun isStringly(memsizer: IMemSizer): Boolean =
base == BaseDataType.STR ||
(memsizer.POINTER_MEM_SIZE == 2u && base == BaseDataType.UWORD) ||
(memsizer.POINTER_MEM_SIZE > 2u && base == BaseDataType.LONG) ||
(base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
fun isSplitWordArray(memsizer: IMemSizer): Boolean {
return if(base== BaseDataType.ARRAY_SPLITW)
true
else if(base==BaseDataType.ARRAY_POINTER)
memsizer.POINTER_MEM_SIZE<=2u
else false
}
val isSplitUnsignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.WORD
val isIterable = base.isIterable
val isPassByRef = base.isPassByRef
val isPassByValue = base.isPassByValue
+45 -2
View File
@@ -4,7 +4,11 @@ package prog8.code.core
enum class CpuRegister {
A,
X,
Y
Y,
// m68k:
D0, D1, D2, D3, D4, D5, D6, D7,
A0, A1, A2, A3, A4, A5, A6,
FP0, FP1, FP2, FP3, FP4, FP5, FP6, FP7
}
enum class RegisterOrPair {
@@ -20,7 +24,12 @@ enum class RegisterOrPair {
R0, R1, R2, R3, R4, R5, R6, R7,
R8, R9, R10, R11, R12, R13, R14, R15,
// combined virtual registers to store 32 bits longs:
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15;
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15,
// m68k data & address registers:
D0, D1, D2, D3, D4, D5, D6, D7,
A0, A1, A2, A3, A4, A5, A6,
// m68k FPU registers:
FP0, FP1, FP2, FP3, FP4, FP5, FP6, FP7;
companion object {
val names: Set<String> = entries.map { it.toString() }.toSet()
@@ -29,6 +38,29 @@ enum class RegisterOrPair {
CpuRegister.A -> A
CpuRegister.X -> X
CpuRegister.Y -> Y
CpuRegister.D0 -> D0
CpuRegister.D1 -> D1
CpuRegister.D2 -> D2
CpuRegister.D3 -> D3
CpuRegister.D4 -> D4
CpuRegister.D5 -> D5
CpuRegister.D6 -> D6
CpuRegister.D7 -> D7
CpuRegister.A0 -> A0
CpuRegister.A1 -> A1
CpuRegister.A2 -> A2
CpuRegister.A3 -> A3
CpuRegister.A4 -> A4
CpuRegister.A5 -> A5
CpuRegister.A6 -> A6
CpuRegister.FP0 -> FP0
CpuRegister.FP1 -> FP1
CpuRegister.FP2 -> FP2
CpuRegister.FP3 -> FP3
CpuRegister.FP4 -> FP4
CpuRegister.FP5 -> FP5
CpuRegister.FP6 -> FP6
CpuRegister.FP7 -> FP7
}
}
}
@@ -118,6 +150,15 @@ val CombinedLongRegisters = setOf(
RegisterOrPair.R14R15
)
val M68kRegisters = setOf(
RegisterOrPair.D0, RegisterOrPair.D1, RegisterOrPair.D2, RegisterOrPair.D3,
RegisterOrPair.D4, RegisterOrPair.D5, RegisterOrPair.D6, RegisterOrPair.D7,
RegisterOrPair.A0, RegisterOrPair.A1, RegisterOrPair.A2, RegisterOrPair.A3,
RegisterOrPair.A4, RegisterOrPair.A5, RegisterOrPair.A6,
RegisterOrPair.FP0, RegisterOrPair.FP1, RegisterOrPair.FP2, RegisterOrPair.FP3,
RegisterOrPair.FP4, RegisterOrPair.FP5, RegisterOrPair.FP6, RegisterOrPair.FP7
)
val CpuRegisters = setOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
@@ -128,6 +169,8 @@ enum class OutputType {
RAW,
PRG,
XEX,
ELF,
AMIGAHUNK,
LIBRARY
}
@@ -2,10 +2,67 @@ package prog8.code.core
import java.nio.file.Path
enum class CpuType {
CPU6502,
CPU65C02,
VIRTUAL
/**
* Identifies the target CPU type and its key characteristics that affect IR semantics.
*
* The most important property is [statusBitsOnMultiByteOps], which controls the IR
* status-bit contract (see IRInstructions.kt for the full spec):
*
* - When `true`, the IR generator is allowed to skip emitting explicit CMP/CMPI before
* branches that depend on the result of a previous "status-setting" instruction
* (LOAD, INC, DEC, AND, OR, XOR, etc.). The contract is: "this instruction sets
* Z and N based on the full multi-byte value." Backends for these targets must
* honor this contract (e.g., M68000's `move.w #imm,d0` naturally sets Z based on
* the 16-bit value, so the IR can rely on it).
*
* - When `false`, multi-byte operations on this target do NOT set status bits
* correctly for the full value (e.g., 6502's `dec lo` only sets Z based on the
* low byte of a 16-bit decrement). The IR generator must therefore always emit
* an explicit CMP/CMPI before any branch that depends on the result. Backends
* for these targets should NOT add `ora` postambles to set Z; instead they
* rely on the explicit CMP/CMPI emitted by the IR generator.
*
* For 8-bit CPUs (6502, 65C02) the natural status flags from a multi-byte
* cascade are misleading: Z is set based on the LAST byte operated on, not
* the full value. So we treat the contract as "no instruction sets status
* bits except CMP/CMPI/SEC/CLC/SGN/BITTST" and have the IR generator emit
* explicit compares. This avoids the trap of "this works for BYTE but
* silently breaks for WORD/LONG".
*/
enum class CpuType(val statusBitsOnMultiByteOps: Boolean) {
/** 8-bit NMOS 6502. Multi-byte ops don't set Z based on full value. */
CPU6502(false),
/** 8-bit CMOS 65C02. Same status-bit issue as plain 6502. */
CPU65C02(false),
/** 16/32 bit Motorola 68000. has single instructions that set status bits when dealing with multi byte operands */
M68000(true),
/** 32 bit Motorola 68020. has single instructions that set status bits when dealing with multi byte operands */
M68020(true),
/**
* Virtual machine target. We use `false` here so the IR generator always
* emits explicit CMP/CMPI; the VM's setResultReg then does NOT set Z/N
* flags (only CMP/CMPI/SEC/CLC/SGN/BITTST do). This matches the VM's
* existing documentation (VirtualMachine.kt top comment) and keeps the
* behavior consistent across all current targets.
*/
VIRTUAL(false)
;
/** True for the 6502-family targets (plain 6502 and 65C02). */
val is6502: Boolean
get() = this == CPU6502 || this == CPU65C02
val is68k: Boolean
get() = this == M68000 || this == M68020
/** True for targets that use the m68k-style hardware-register calling convention (D0-D7, A0-A6, FP0-FP7). */
val usesM68kConvention: Boolean
get() = is68k || this == VIRTUAL
/** True for big-endian targets. The 6502 family and the VM are little-endian. */
val isBigEndian: Boolean
get() = this == M68000 || this == M68020
}
interface ICompilationTarget: IStringEncoding, IMemSizer {
@@ -13,8 +70,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: UInt
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
val ARRAY_SIZE_LIMIT: UInt
val PROGRAM_LOAD_ADDRESS : UInt
val PROGRAM_MEMTOP_ADDRESS: UInt
val BSSHIGHRAM_START: UInt
@@ -28,6 +84,8 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val customLauncher: List<String>
val additionalAssemblerOptions: List<String>
val defaultOutputType: OutputType
val defaultLauncherType: CbmPrgLauncherType
val supportsBankedCalls: Boolean
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
fun getFloatAsmBytes(num: Number): String
+14 -1
View File
@@ -1,15 +1,28 @@
package prog8.code.core
interface IMemSizer {
val FLOAT_MEM_SIZE: UInt
val POINTER_MEM_SIZE: UInt
val pointerType: DataType
get() = if(POINTER_MEM_SIZE > 2u) DataType.LONG else DataType.UWORD
val pointerBaseType: BaseDataType
get() = if(POINTER_MEM_SIZE > 2u) BaseDataType.LONG else BaseDataType.UWORD
fun memorySize(dt: DataType, numElements: Int?): Int
fun memorySize(dt: BaseDataType): Int {
if(dt.isPassByRef)
return memorySize(DataType.UWORD, null) // a pointer size
return POINTER_MEM_SIZE.toInt()
try {
return memorySize(DataType.forDt(dt), null)
} catch (x: NoSuchElementException) {
throw IllegalArgumentException(x.message)
}
}
fun isSplitWordArray(dt: DataType): Boolean {
return dt.isSplitWordArray(this)
}
}
@@ -33,6 +33,26 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
val allocatedVariables = mutableMapOf<String, VarAllocation>()
val free = mutableSetOf<UInt>() // subclasses must set this to the appropriate free locations.
/**
* Report a warning for every scratch byte that is excluded from allocation by a %zpreserved
* or %zpallowed directive. The compiler's code generators always use these fixed scratch
* locations internally (P8ZP_SCRATCH_*), so a %zpreserved/%zpallowed range that excludes one
* of them would let the compiler overwrite memory the user explicitly wants to protect.
*/
fun checkScratchConflicts(errors: IErrorReporter) {
if(options.zeropage== ZeropageType.DONTUSE)
return
val scratchBytes = listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR + 1u)
for(byte in scratchBytes) {
val preserved = options.zpReserved.any { byte in it }
val allowed = options.zpAllowed.any { byte in it }
if(preserved || !allowed)
errors.warn("zero-page address $${byte.toString(16)} is a fixed scratch location used by the compiler (P8ZP_SCRATCH_*), but it is excluded by a %zpreserved or %zpallowed directive", Position.DUMMY)
}
}
fun removeReservedFromFreePool() {
for (reserved in options.zpReserved)
reserve(reserved)
@@ -13,9 +13,10 @@ import kotlin.io.path.Path
object ImportFileSystem {
val userHome: Path get() = Path.of(System.getProperty("user.home"))
fun expandTilde(path: String): String = if (path.startsWith("~")) {
val userHome = System.getProperty("user.home")
userHome + path.drop(1)
userHome.toString() + path.drop(1)
} else {
path
}
@@ -35,6 +36,11 @@ object ImportFileSystem {
}
}
fun clearCaches() {
cache.clear()
lineSpanCache.clear()
}
fun retrieveSourceLine(position: Position): String {
if(SourceCode.isLibraryResource(position.file)) {
val key = SourceCode.withoutPrefix(position.file)
@@ -0,0 +1,107 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import java.nio.file.Path
class Amiga1200Target: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(4u, 4u) {
override val name = NAME
override val supportsBankedCalls = true
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.AMIGAHUNK
override val defaultLauncherType = CbmPrgLauncherType.NONE
companion object {
const val NAME = "amiga1200"
/** needed to map bank numbers to libraries in extsub/CALLFAR library calls */
val LibraryNumbers = Amiga500Target.LibraryNumbers
}
override val cpu = CpuType.M68020
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
override val FLOAT_MEM_SIZE = 4u
override val POINTER_MEM_SIZE = 4u
override val ARRAY_SIZE_LIMIT = 32768u
override val PROGRAM_LOAD_ADDRESS = 0u // amiga executables are relocatable
override val PROGRAM_MEMTOP_ADDRESS = 0xfffffffeu // not actually used
override val BSSHIGHRAM_START = 0u // not used
override val BSSHIGHRAM_END = 0u // not used
override val BSSGOLDENRAM_START = 0u // not used
override val BSSGOLDENRAM_END = 0u // not used
override lateinit var zeropage: Zeropage
override fun getFloatAsmBytes(num: Number): String {
TODO("float asm bytes")
}
override fun convertFloatToBytes(num: Double): List<UByte> {
TODO("convert float to bytes")
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==4) { "need 4 bytes" }
TODO("convert bytes to float")
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
if(selectedEmulator!=1) {
System.err.println("The amiga1200 target only supports the main emulator (Amitools's vamos).")
return
}
val cpuStr = when(this.cpu) {
CpuType.M68000 -> "68000"
CpuType.M68020 -> "68020"
else -> error("invalid cpu type")
}
val exeFile = programNameWithPath.resolveSibling("${programNameWithPath.fileName}")
val cmd =
listOf(
"vamos",
"--cpu", cpuStr,
exeFile.toString(),
)
if(!quiet)
println("Launching Amitools's Vamos...")
val pb = ProcessBuilder(cmd).inheritIO()
try {
pb.start().waitFor()
} catch(_: Exception) {
System.err.println("Cannot find 'vamos' (Amitools's Amiga emulator). Install it from: https://github.com/cnvogelg/amitools")
}
}
override fun isIOAddress(address: UInt): Boolean =
address in 0xbfd000u..0xbfdfffu || // CIA-B
address in 0xbfe000u..0xbfefffu || // CIA-A
address in 0xdff000u..0xdff1ffu || // Custom chipset (Paula, Alice, Lisa, Denise)
address in 0xe80000u..0xefffffu // Zorro II autoconfig space
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = Amiga1200Zeropage(compilerOptions)
}
}
private class Amiga1200Zeropage(options: CompilationOptions): Zeropage(options) {
override val SCRATCH_B1: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_REG: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_W1: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_W2: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_PTR: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
}
@@ -0,0 +1,144 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import java.nio.file.Path
class Amiga500Target: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(4u, 4u) {
override val name = NAME
override val supportsBankedCalls = true
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.AMIGAHUNK
override val defaultLauncherType = CbmPrgLauncherType.NONE
companion object {
const val NAME = "amiga500"
/** needed to map bank numbers to libraries in extsub/CALLFAR library calls */
val LibraryNumbers = mapOf(
1 to "Sys", // exec
2 to "DOS",
3 to "Gfx",
4 to "Intuition",
5 to "GadTools",
6 to "Layers",
7 to "Asl",
8 to "ConsoleDevice",
9 to "Utility",
10 to "Expansion",
11 to "Icon",
12 to "Workbench",
13 to "Diskfont",
14 to "IFFParse",
15 to "Input",
16 to "Keymap",
17 to "Locale",
18 to "Timer",
19 to "LowLevel",
20 to "Math",
21 to "MathIeeeSingBas",
22 to "MathIeeeSingTrans",
23 to "MathIeeeDoubBas",
24 to "MathIeeeDoubTrans",
25 to "MathTrans",
26 to "NV",
27 to "RealTime",
28 to "Translator",
29 to "RexxSys",
30 to "Cx",
31 to "DataTypes",
32 to "Disk",
33 to "AmigaGuide",
34 to "ARexx",
35 to "BattClock",
36 to "BattMem"
)
}
override val cpu = CpuType.M68000
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
override val FLOAT_MEM_SIZE = 4u
override val POINTER_MEM_SIZE = 4u
override val ARRAY_SIZE_LIMIT = 32768u
override val PROGRAM_LOAD_ADDRESS = 0u // amiga executables are relocatable
override val PROGRAM_MEMTOP_ADDRESS = 0xfffffffeu // not actually used
override val BSSHIGHRAM_START = 0u // not used
override val BSSHIGHRAM_END = 0u // not used
override val BSSGOLDENRAM_START = 0u // not used
override val BSSGOLDENRAM_END = 0u // not used
override lateinit var zeropage: Zeropage
override fun getFloatAsmBytes(num: Number): String {
TODO("float asm bytes")
}
override fun convertFloatToBytes(num: Double): List<UByte> {
TODO("convert float to bytes")
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==4) { "need 4 bytes" }
TODO("convert bytes to float")
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
if(selectedEmulator!=1) {
System.err.println("The amiga500 target only supports the main emulator (Amitools's vamos).")
return
}
val cpuStr = when(this.cpu) {
CpuType.M68000 -> "68000"
CpuType.M68020 -> "68020"
else -> error("invalid cpu type")
}
val exeFile = programNameWithPath.resolveSibling("${programNameWithPath.fileName}")
val cmd =
listOf(
"vamos",
"--cpu", cpuStr,
exeFile.toString(),
)
if(!quiet)
println("Launching Amitools's Vamos...")
val pb = ProcessBuilder(cmd).inheritIO()
try {
pb.start().waitFor()
} catch(e: Exception) {
System.err.println("Cannot find 'vamos' (Amitools's Amiga emulator). Install it from: https://github.com/cnvogelg/amitools")
}
}
override fun isIOAddress(address: UInt): Boolean =
address in 0xbfd000u..0xbfdfffu || // CIA-B
address in 0xbfe000u..0xbfefffu || // CIA-A
address in 0xdff000u..0xdff1ffu || // Custom chipset (Paula, Alice, Lisa, Denise)
address in 0xe80000u..0xefffffu // Zorro II autoconfig space
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = AmigaZeropage(compilerOptions)
}
}
private class AmigaZeropage(options: CompilationOptions): Zeropage(options) {
override val SCRATCH_B1: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_REG: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_W1: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_W2: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_PTR: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
}
+5 -2
View File
@@ -8,14 +8,16 @@ import java.nio.file.Path
class C128Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
override val name = NAME
override val supportsBankedCalls = true
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "c128"
@@ -27,7 +29,8 @@ class C128Target: ICompilationTarget,
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val POINTER_MEM_SIZE = 2u
override val ARRAY_SIZE_LIMIT = 256u
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
+12 -3
View File
@@ -9,14 +9,16 @@ import java.nio.file.Path
class C64Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
override val name = NAME
override val supportsBankedCalls = true
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "c64"
@@ -29,8 +31,9 @@ class C64Target: ICompilationTarget,
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val POINTER_MEM_SIZE = 2u
override val ARRAY_SIZE_LIMIT = 256u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
@@ -96,6 +99,9 @@ val CompilationTargets = listOf(
C128Target.NAME,
Cx16Target.NAME,
PETTarget.NAME,
Qemu68kTarget.NAME,
Amiga500Target.NAME,
Amiga1200Target.NAME,
VMTarget.NAME
)
@@ -104,6 +110,9 @@ fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
C128Target.NAME -> C128Target()
Cx16Target.NAME -> Cx16Target()
PETTarget.NAME -> PETTarget()
Qemu68kTarget.NAME -> Qemu68kTarget()
Amiga500Target.NAME -> Amiga500Target()
Amiga1200Target.NAME -> Amiga1200Target()
VMTarget.NAME -> VMTarget()
else -> throw IllegalArgumentException("invalid compilation target")
}
@@ -15,19 +15,21 @@ import kotlin.io.path.nameWithoutExtension
class ConfigFileTarget(
override val name: String,
override val supportsBankedCalls: Boolean,
override val defaultEncoding: Encoding,
override val cpu: CpuType,
override val PROGRAM_LOAD_ADDRESS: UInt,
override val PROGRAM_MEMTOP_ADDRESS: UInt,
override val STARTUP_CODE_RESERVED_SIZE: UInt,
override val BSSHIGHRAM_START: UInt,
override val BSSHIGHRAM_END: UInt,
override val BSSGOLDENRAM_START: UInt,
override val BSSGOLDENRAM_END: UInt,
override val defaultOutputType: OutputType,
override val defaultLauncherType: CbmPrgLauncherType,
override val libraryPath: Path,
override val customLauncher: List<String>,
override val additionalAssemblerOptions: List<String>,
val varsAddress: UInt? = null,
val ioAddresses: List<UIntRange>,
val zpScratchB1: UInt,
val zpScratchReg: UInt,
@@ -37,8 +39,13 @@ class ConfigFileTarget(
val virtualregistersStart: UInt,
val zpFullsafe: List<UIntRange>,
val zpKernalsafe: List<UIntRange>,
val zpBasicsafe: List<UIntRange>
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) {
val zpBasicsafe: List<UIntRange>,
val pointerSize: Int = 2
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8u, pointerSize.toUInt()) {
init {
require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" }
}
companion object {
@@ -65,6 +72,12 @@ class ConfigFileTarget(
return value.toUInt()
}
private fun parsePointerSize(props: Properties): Int {
val pointerSize = parseInt(props.getProperty("pointer_size", "2")).toInt()
require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" }
return pointerSize
}
private fun parseAddressRanges(key: String, props: Properties): List<UIntRange> {
val rangesStr = props.getString(key)
if(rangesStr.isBlank())
@@ -110,24 +123,35 @@ class ConfigFileTarget(
(customLauncherStr+"\n").lines().map { it.trimEnd() }
else emptyList()
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
val pointerSize = parsePointerSize(props)
val outputTypeString = props.getProperty("output_type", "PRG")
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
val launcherString = props.getProperty("launcher", "BASIC")
val defaultLauncherType = try {
CbmPrgLauncherType.valueOf(launcherString.uppercase())
} catch (_: IllegalArgumentException) {
CbmPrgLauncherType.BASIC
}
val varsAddress = props.getProperty("vars_address")?.let { parseInt(it) }
return ConfigFileTarget(
configfile.nameWithoutExtension,
props.getProperty("supports_banked_calls", "false").toBoolean(),
Encoding.entries.first { it.prefix==props.getString("encoding") },
cpuType,
props.getInteger("load_address"),
props.getInteger("memtop"),
0u, // used only in a very specific error condition check in a certain scenario...
props.getInteger("bss_highram_start"),
props.getInteger("bss_highram_end"),
props.getInteger("bss_goldenram_start"),
props.getInteger("bss_goldenram_end"),
defaultOutputType,
defaultLauncherType,
libraryPath,
customLauncher,
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
varsAddress,
ioAddresses,
props.getInteger("zp_scratch_b1"),
props.getInteger("zp_scratch_reg"),
@@ -138,6 +162,7 @@ class ConfigFileTarget(
zpFullsafe,
zpKernalsafe,
zpBasicsafe,
pointerSize,
)
}
}
@@ -146,7 +171,8 @@ class ConfigFileTarget(
override val FLOAT_MAX_POSITIVE = 9.999999999e97
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
override val FLOAT_MEM_SIZE = 8u
override val POINTER_MEM_SIZE = pointerSize.toUInt()
override val ARRAY_SIZE_LIMIT = 256u
override lateinit var zeropage: Zeropage
override fun getFloatAsmBytes(num: Number) = TODO("floats")
+5 -2
View File
@@ -8,14 +8,16 @@ import java.nio.file.Path
class Cx16Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
override val name = NAME
override val supportsBankedCalls = true
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "cx16"
@@ -27,7 +29,8 @@ class Cx16Target: ICompilationTarget,
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val POINTER_MEM_SIZE = 2u
override val ARRAY_SIZE_LIMIT = 256u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
+2 -2
View File
@@ -9,7 +9,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
companion object {
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
const val FLOAT_MEM_SIZE = 5
const val FLOAT_MEM_SIZE = 5u
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
fun fromNumber(num: Number): Mflpt5 {
@@ -73,4 +73,4 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
val b4 = "$" + b4.toString(16).padStart(2, '0')
return "$b0, $b1, $b2, $b3, $b4"
}
}
}
@@ -4,36 +4,42 @@ import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.core.IMemSizer
internal class NormalMemSizer(val floatsize: Int): IMemSizer {
internal class NormalMemSizer(override val FLOAT_MEM_SIZE: UInt, override val POINTER_MEM_SIZE: UInt): IMemSizer {
private val pointerSize = POINTER_MEM_SIZE.toInt()
private val floatSize = FLOAT_MEM_SIZE.toInt()
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isPointerArray)
return 2 * numElements!! // array of pointers is just array of uwords
return pointerSize * numElements!! // array of pointers
else if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
if(numElements==null) return pointerSize // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
BaseDataType.STR, BaseDataType.POINTER -> numElements * pointerSize
BaseDataType.LONG -> numElements * 4
BaseDataType.FLOAT-> numElements * floatsize
BaseDataType.FLOAT-> numElements * floatSize
BaseDataType.STRUCT_INSTANCE -> numElements * (dt.subType?.memsize(this) ?: throw IllegalArgumentException("struct instance array has unresolved subtype"))
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
}
}
else if (dt.isString) {
return numElements // treat it as the size of the given string with the length
?: 2 // treat it as the size to store a string pointer
?: pointerSize // treat it as the size to store a string pointer
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> floatsize * (numElements ?: 1)
dt.isFloat -> floatSize * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isPointer -> 2 // pointer is just a uword
dt.isStructInstance -> dt.subType!!.memsize(this)
dt.isWord -> 2 * (numElements ?: 1)
dt.isPointer -> pointerSize * (numElements ?: 1)
dt.isStructInstance -> dt.subType?.memsize(this) ?: throw IllegalArgumentException("struct instance has unresolved subtype")
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
else -> throw IllegalArgumentException("invalid dt $dt")
}
}
}
}
+5 -2
View File
@@ -8,14 +8,16 @@ import java.nio.file.Path
class PETTarget: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
override val name = NAME
override val supportsBankedCalls = false
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "pet32"
@@ -26,7 +28,8 @@ class PETTarget: ICompilationTarget,
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val POINTER_MEM_SIZE = 2u
override val ARRAY_SIZE_LIMIT = 256u
override val PROGRAM_LOAD_ADDRESS = 0x0401u
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
@@ -0,0 +1,125 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import java.nio.file.Path
/**
* For now target a m68020 cpu
* Eventually the goal is to be able to create programs for the Amiga A1200 (68020 cpu) or A500 (68000 cpu)
*/
class Qemu68kTarget: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(4u, 4u) {
override val name = NAME
override val supportsBankedCalls = false
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.ELF
override val defaultLauncherType = CbmPrgLauncherType.NONE
companion object {
const val NAME = "qemu68k"
}
override val cpu = CpuType.M68020
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
override val FLOAT_MEM_SIZE = 4u
override val POINTER_MEM_SIZE = 4u
override val ARRAY_SIZE_LIMIT = 32768u
override val PROGRAM_LOAD_ADDRESS = 0x10000u
override val PROGRAM_MEMTOP_ADDRESS = 0x00100000u // TODO hardcoded at 1 Mb of RAM for now... it starts at $0
override val BSSHIGHRAM_START = 0u // not actually used
override val BSSHIGHRAM_END = 0u // not actually used
override val BSSGOLDENRAM_START = 0u // not actually used
override val BSSGOLDENRAM_END = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override fun getFloatAsmBytes(num: Number): String {
TODO("float asm bytes")
}
override fun convertFloatToBytes(num: Double): List<UByte> {
TODO("convert float to bytes")
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==4) { "need 4 bytes" }
TODO("convert bytes to float")
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
if(selectedEmulator!=1) {
System.err.println("The qemu68k target only supports the main emulator (Qemu).")
return
}
val elfFile = programNameWithPath.resolveSibling("${programNameWithPath.fileName}.elf")
val binFile = programNameWithPath.resolveSibling("${programNameWithPath.fileName}.bin")
val isElf = elfFile.toFile().exists()
val cpuStr = this.cpu.toString().lowercase()
val cmd = if (isElf) {
listOf(
"qemu-system-m68k",
"-M", "virt",
"-cpu", cpuStr,
"-m", "1M",
"-kernel", elfFile.toString(),
"-nographic"
)
} else if (binFile.toFile().exists()) {
val loadAddr = PROGRAM_LOAD_ADDRESS.toInt()
listOf(
"qemu-system-m68k",
"-M", "virt",
"-cpu", cpuStr,
"-m", "1M",
"-device", "loader,file=${binFile},addr=0x${loadAddr.toString(16)},cpu-num=0",
"-nographic"
)
} else {
System.err.println("No .elf or .bin file found for ${programNameWithPath.fileName}")
return
}
if(!quiet) {
val launchMsg = if (isElf) "ELF" else "raw binary"
println("Launching QEMU (press Ctrl-A X to exit)... (from $launchMsg)")
}
val pb = ProcessBuilder(cmd).inheritIO()
try {
pb.start().waitFor()
} catch (_: java.io.IOException) {
System.err.println("Cannot launch qemu-system-m68k. Install it via your package manager, e.g.:")
System.err.println(" sudo apt install qemu-system-m68k # Debian/Ubuntu")
System.err.println(" sudo pacman -S qemu-system-m68k # Arch Linux")
System.err.println("or build it from source: https://www.qemu.org/download/")
}
}
override fun isIOAddress(address: UInt): Boolean = address>=0xff000000u
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = M68kZeropage(compilerOptions)
}
}
private class M68kZeropage(options: CompilationOptions): Zeropage(options) {
override val SCRATCH_B1: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_REG: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_W1: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_W2: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
override val SCRATCH_PTR: UInt
get() = throw IllegalStateException("m68k shouldn't use this zeropage variable")
}
+10 -6
View File
@@ -10,28 +10,32 @@ import kotlin.io.path.readText
class VMTarget: ICompilationTarget,
IStringEncoding by Encoder(false),
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
IMemSizer by NormalMemSizer(8u, 4u) {
override val name = NAME
override val supportsBankedCalls = false
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "virtual"
const val FLOAT_MEM_SIZE = 8 // 64-bits double
const val RAM_SIZE = 16 * 1024 * 1024 // 16 MB
}
override val cpu = CpuType.VIRTUAL
override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE
override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
override val FLOAT_MEM_SIZE = 8u
override val POINTER_MEM_SIZE = 4u
override val ARRAY_SIZE_LIMIT = 65535u
override val PROGRAM_LOAD_ADDRESS = 0u
override val PROGRAM_MEMTOP_ADDRESS = 0xffffffu
override val BSSHIGHRAM_START = 0u // not actually used
override val BSSHIGHRAM_END = 0u // not actually used
@@ -8,7 +8,7 @@ import java.io.CharConversionException
object PetsciiEncoding {
// decoding: from Petscii/Screencodes (0-255) to Unicode
// character tables used from https://github.com/irmen/cbmcodecs2
// character tables used from https://codeberg.org/irmen/cbmcodecs2.git
private val decodingPetsciiLowercase = charArrayOf(
'\u0000', // 0x00 -> \u0000
@@ -32,7 +32,10 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
}
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE -> {
free.addAll(0xb1u..0xbau) // TODO more?
// Only scratch register area is safe for transient use.
// $b5 and $ba are clobbered by PET BASIC/Kernal calls like CHROUT ($ffd2).
// User variables requiring persistent storage must NOT be placed here.
free.addAll(listOf(0xb1u, 0xb2u, 0xb3u, 0xb4u, 0xb6u, 0xb7u, 0xb8u, 0xb9u, 0xfbu, 0xfcu, 0xfdu, 0xfeu))
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
@@ -83,4 +86,4 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
}
}
}
@@ -8,6 +8,9 @@ import prog8.code.core.*
* Returns standard memory sizes for all data types.
*/
object DummyMemsizer : IMemSizer {
override val FLOAT_MEM_SIZE: UInt = 4u
override val POINTER_MEM_SIZE: UInt = 2u
override fun memorySize(dt: DataType, numElements: Int?): Int {
if (dt.isPointerArray)
return 2 * numElements!!
@@ -18,6 +18,7 @@ class ErrorReporterForTests(
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
val infos = mutableListOf<String>()
val printedErrors = mutableListOf<String>()
override fun err(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
@@ -47,7 +48,7 @@ class ErrorReporterForTests(
!errors.any { ":${position.line}:" in it }
override fun printSingleError(errormessage: String) {
// prints nothing in tests
printedErrors.add(errormessage)
}
override fun report() {
@@ -0,0 +1,66 @@
package prog8tests.codecore
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.core.BaseDataType
import prog8.code.target.ConfigFileTarget
import java.nio.file.Files
import kotlin.io.path.writeText
class TestConfigFileTarget: FunSpec({
fun loadTarget(extraProperties: String = ""): ConfigFileTarget {
val directory = Files.createTempDirectory("prog8-config-target")
val library = Files.createDirectories(directory.resolve("library"))
val config = directory.resolve("target.properties")
config.writeText(
"""
cpu = 65C02
encoding = iso
load_address = 2048
memtop = 65535
bss_highram_start = 0
bss_highram_end = 0
bss_goldenram_start = 0
bss_goldenram_end = 0
zp_scratch_b1 = 240
zp_scratch_reg = 241
zp_scratch_w1 = 242
zp_scratch_w2 = 244
zp_scratch_ptr = 246
virtual_registers = 2
zp_fullsafe = 34-239
zp_kernalsafe = 34-239
zp_basicsafe = 34-239
io_regions =
""".trimIndent() + "\nlibrary = $library\n$extraProperties"
)
return try {
ConfigFileTarget.fromConfigFile(config)
} finally {
directory.toFile().deleteRecursively()
}
}
test("legacy config defaults to 2-byte pointers and 256 element arrays") {
val target = loadTarget()
target.POINTER_MEM_SIZE shouldBe 2u
target.memorySize(BaseDataType.POINTER) shouldBe 2
target.ARRAY_SIZE_LIMIT shouldBe 256u
}
test("config supports 4-byte pointers") {
val target = loadTarget("pointer_size = 4")
target.POINTER_MEM_SIZE shouldBe 4u
target.memorySize(BaseDataType.POINTER) shouldBe 4
target.ARRAY_SIZE_LIMIT shouldBe 256u
}
test("config rejects unsupported pointer sizes") {
shouldThrow<IllegalArgumentException> {
loadTarget("pointer_size = 3")
}
}
})
@@ -23,31 +23,31 @@ class TestConversions: FunSpec({
}
test("toHex for 16-255 (hex with $)") {
16.toHex() shouldBe "\$10"
255.toHex() shouldBe "\$ff"
16.toHex() shouldBe $$"$10"
255.toHex() shouldBe $$"$ff"
}
test("toHex for 256-65535 (4 digit hex)") {
256.toHex() shouldBe "\$0100"
65535.toHex() shouldBe "\$ffff"
256.toHex() shouldBe $$"$0100"
65535.toHex() shouldBe $$"$ffff"
}
test("toHex for larger numbers (8 digit hex)") {
65536.toHex() shouldBe "\$00010000"
0x12345678.toHex() shouldBe "\$12345678"
65536.toHex() shouldBe $$"$00010000"
0x12345678.toHex() shouldBe $$"$12345678"
}
test("toHex for negative numbers") {
(-1).toHex() shouldBe "-1"
(-255).toHex() shouldBe "-\$ff"
Int.MIN_VALUE.toHex() shouldBe "\$80000000"
(-255).toHex() shouldBe $$"-$ff"
Int.MIN_VALUE.toHex() shouldBe $$"$80000000"
}
test("UInt.toHex") {
0u.toHex() shouldBe "0"
15u.toHex() shouldBe "15"
255u.toHex() shouldBe "\$ff"
65535u.toHex() shouldBe "\$ffff"
255u.toHex() shouldBe $$"$ff"
65535u.toHex() shouldBe $$"$ffff"
}
// ============================================================================
+120 -91
View File
@@ -2,22 +2,28 @@ package prog8tests.codecore
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.code.core.*
import prog8.code.target.Amiga500Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
/**
* Unit tests for DataType and BaseDataType classes in codeCore.
* Focus on type predicates, relationships, and key operations.
*/
class TestDataType: FunSpec({
val tgt = Cx16Target()
val dummyMemSizer = object : IMemSizer {
override val FLOAT_MEM_SIZE: UInt = 4u
override val POINTER_MEM_SIZE: UInt = 2u
override fun memorySize(dt: DataType, numElements: Int?): Int {
if (dt.isPointerArray) return 2 * numElements!!
if (dt.isArray || dt.isSplitWordArray) {
if (dt.isArray || dt.isSplitWordArray(this)) {
require(numElements != null)
return when (dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
@@ -45,61 +51,69 @@ class TestDataType: FunSpec({
// ============================================================================
test("isByte extension") {
BaseDataType.UBYTE.isByte.shouldBeTrue()
BaseDataType.BYTE.isByte.shouldBeTrue()
BaseDataType.UWORD.isByte.shouldBeFalse()
BaseDataType.WORD.isByte.shouldBeFalse()
BaseDataType.UBYTE.isByte shouldBe true
BaseDataType.BYTE.isByte shouldBe true
BaseDataType.UWORD.isByte shouldBe false
BaseDataType.WORD.isByte shouldBe false
}
test("isByteOrBool extension") {
BaseDataType.UBYTE.isByteOrBool.shouldBeTrue()
BaseDataType.BYTE.isByteOrBool.shouldBeTrue()
BaseDataType.BOOL.isByteOrBool.shouldBeTrue()
BaseDataType.UWORD.isByteOrBool.shouldBeFalse()
BaseDataType.UBYTE.isByteOrBool shouldBe true
BaseDataType.BYTE.isByteOrBool shouldBe true
BaseDataType.BOOL.isByteOrBool shouldBe true
BaseDataType.UWORD.isByteOrBool shouldBe false
}
test("isWord extension") {
BaseDataType.UWORD.isWord.shouldBeTrue()
BaseDataType.WORD.isWord.shouldBeTrue()
BaseDataType.UBYTE.isWord.shouldBeFalse()
BaseDataType.UWORD.isWord shouldBe true
BaseDataType.WORD.isWord shouldBe true
BaseDataType.UBYTE.isWord shouldBe false
}
test("isInteger extension") {
BaseDataType.UBYTE.isInteger.shouldBeTrue()
BaseDataType.WORD.isInteger.shouldBeTrue()
BaseDataType.LONG.isInteger.shouldBeTrue()
BaseDataType.FLOAT.isInteger.shouldBeFalse()
BaseDataType.UBYTE.isInteger shouldBe true
BaseDataType.WORD.isInteger shouldBe true
BaseDataType.LONG.isInteger shouldBe true
BaseDataType.FLOAT.isInteger shouldBe false
}
test("isNumeric extension") {
BaseDataType.UBYTE.isNumeric.shouldBeTrue()
BaseDataType.FLOAT.isNumeric.shouldBeTrue()
BaseDataType.BOOL.isNumeric.shouldBeFalse()
BaseDataType.UBYTE.isNumeric shouldBe true
BaseDataType.FLOAT.isNumeric shouldBe true
BaseDataType.BOOL.isNumeric shouldBe false
}
test("isSigned extension") {
BaseDataType.BYTE.isSigned.shouldBeTrue()
BaseDataType.WORD.isSigned.shouldBeTrue()
BaseDataType.LONG.isSigned.shouldBeTrue()
BaseDataType.UBYTE.isSigned.shouldBeFalse()
BaseDataType.BYTE.isSigned shouldBe true
BaseDataType.WORD.isSigned shouldBe true
BaseDataType.LONG.isSigned shouldBe true
BaseDataType.UBYTE.isSigned shouldBe false
}
test("isArray extension") {
BaseDataType.ARRAY.isArray.shouldBeTrue()
BaseDataType.ARRAY_SPLITW.isArray.shouldBeTrue()
BaseDataType.ARRAY_POINTER.isArray.shouldBeTrue()
BaseDataType.UBYTE.isArray.shouldBeFalse()
BaseDataType.ARRAY.isArray shouldBe true
BaseDataType.ARRAY_SPLITW.isArray shouldBe true
BaseDataType.ARRAY_POINTER.isArray shouldBe true
BaseDataType.UBYTE.isArray shouldBe false
}
test("isPointer extension") {
BaseDataType.POINTER.isPointer.shouldBeTrue()
BaseDataType.UBYTE.isPointer.shouldBeFalse()
BaseDataType.POINTER.isPointer shouldBe true
BaseDataType.UBYTE.isPointer shouldBe false
}
test("isSplitWordArray extension") {
BaseDataType.ARRAY_SPLITW.isSplitWordArray.shouldBeTrue()
BaseDataType.ARRAY_POINTER.isSplitWordArray.shouldBeTrue()
BaseDataType.ARRAY.isSplitWordArray.shouldBeFalse()
DataType.splitWordArrayFor(BaseDataType.UWORD).isSplitWordArray(Cx16Target()) shouldBe true
DataType.splitWordArrayFor(BaseDataType.UWORD).isSplitWordArray(VMTarget()) shouldBe true
DataType.splitWordArrayFor(BaseDataType.UWORD).isSplitWordArray(Amiga500Target()) shouldBe true
DataType.arrayOfPointersTo(BaseDataType.UWORD).isSplitWordArray(Cx16Target()) shouldBe true
DataType.arrayOfPointersTo(BaseDataType.UWORD).isSplitWordArray(VMTarget()) shouldBe false
DataType.arrayOfPointersTo(BaseDataType.UWORD).isSplitWordArray(Amiga500Target()) shouldBe false
DataType.arrayFor(BaseDataType.UWORD, Cx16Target()).isSplitWordArray(Cx16Target()) shouldBe false
DataType.arrayFor(BaseDataType.UWORD, VMTarget()).isSplitWordArray(VMTarget()) shouldBe false
DataType.arrayFor(BaseDataType.UWORD, Amiga500Target()).isSplitWordArray(Amiga500Target()) shouldBe false
}
// ============================================================================
@@ -113,20 +127,20 @@ class TestDataType: FunSpec({
DataType.forDt(BaseDataType.FLOAT) shouldBe DataType.FLOAT
}
test("DataType.forDt throws for struct instance") {
shouldThrow<NotImplementedError> {
DataType.forDt(BaseDataType.STRUCT_INSTANCE)
}
test("DataType.forDt supports struct instance") {
val dt = DataType.forDt(BaseDataType.STRUCT_INSTANCE)
dt.base shouldBe BaseDataType.STRUCT_INSTANCE
dt.isStructInstance shouldBe true
}
test("DataType.arrayFor creates array types") {
val ubyteArray = DataType.arrayFor(BaseDataType.UBYTE)
val ubyteArray = DataType.arrayFor(BaseDataType.UBYTE, tgt)
ubyteArray.base shouldBe BaseDataType.ARRAY
ubyteArray.sub shouldBe BaseDataType.UBYTE
val wordArraySplit = DataType.arrayFor(BaseDataType.WORD)
wordArraySplit.base shouldBe BaseDataType.ARRAY_SPLITW
wordArraySplit.sub shouldBe BaseDataType.WORD
val wordArray = DataType.arrayFor(BaseDataType.WORD, tgt)
wordArray.base shouldBe BaseDataType.ARRAY
wordArray.sub shouldBe BaseDataType.WORD
}
test("DataType.pointer creates pointer types") {
@@ -140,45 +154,45 @@ class TestDataType: FunSpec({
// ============================================================================
test("DataType.isBasic property") {
DataType.UBYTE.isBasic.shouldBeTrue()
DataType.WORD.isBasic.shouldBeTrue()
DataType.STR.isBasic.shouldBeFalse()
DataType.arrayFor(BaseDataType.UBYTE).isBasic.shouldBeFalse()
DataType.UBYTE.isBasic shouldBe true
DataType.WORD.isBasic shouldBe true
DataType.STR.isBasic shouldBe false
DataType.arrayFor(BaseDataType.UBYTE, tgt).isBasic shouldBe false
}
test("DataType.isByte property") {
DataType.UBYTE.isByte.shouldBeTrue()
DataType.BYTE.isByte.shouldBeTrue()
DataType.WORD.isByte.shouldBeFalse()
DataType.UBYTE.isByte shouldBe true
DataType.BYTE.isByte shouldBe true
DataType.WORD.isByte shouldBe false
}
test("DataType.isNumeric property") {
DataType.UBYTE.isNumeric.shouldBeTrue()
DataType.FLOAT.isNumeric.shouldBeTrue()
DataType.BOOL.isNumeric.shouldBeFalse()
DataType.STR.isNumeric.shouldBeFalse()
DataType.UBYTE.isNumeric shouldBe true
DataType.FLOAT.isNumeric shouldBe true
DataType.BOOL.isNumeric shouldBe false
DataType.STR.isNumeric shouldBe false
}
test("DataType.isArray property") {
DataType.arrayFor(BaseDataType.UBYTE).isArray.shouldBeTrue()
DataType.UBYTE.isArray.shouldBeFalse()
DataType.arrayFor(BaseDataType.UBYTE, tgt).isArray shouldBe true
DataType.UBYTE.isArray shouldBe false
}
test("DataType.isPointer property") {
DataType.pointer(BaseDataType.UBYTE).isPointer.shouldBeTrue()
DataType.UBYTE.isPointer.shouldBeFalse()
DataType.pointer(BaseDataType.UBYTE).isPointer shouldBe true
DataType.UBYTE.isPointer shouldBe false
}
test("DataType.isString property") {
DataType.STR.isString.shouldBeTrue()
DataType.UBYTE.isString.shouldBeFalse()
DataType.STR.isString shouldBe true
DataType.UBYTE.isString shouldBe false
}
test("DataType.isPassByRef property") {
DataType.STR.isPassByRef.shouldBeTrue()
DataType.arrayFor(BaseDataType.UBYTE).isPassByRef.shouldBeTrue()
DataType.UBYTE.isPassByRef.shouldBeFalse()
DataType.pointer(BaseDataType.UBYTE).isPassByRef.shouldBeFalse()
DataType.STR.isPassByRef shouldBe true
DataType.arrayFor(BaseDataType.UBYTE, tgt).isPassByRef shouldBe true
DataType.UBYTE.isPassByRef shouldBe false
DataType.pointer(BaseDataType.UBYTE).isPassByRef shouldBe false
}
// ============================================================================
@@ -186,8 +200,8 @@ class TestDataType: FunSpec({
// ============================================================================
test("DataType.elementType returns element type for arrays") {
DataType.arrayFor(BaseDataType.UBYTE).elementType() shouldBe DataType.UBYTE
DataType.arrayFor(BaseDataType.WORD).elementType() shouldBe DataType.WORD
DataType.arrayFor(BaseDataType.UBYTE, tgt).elementType() shouldBe DataType.UBYTE
DataType.arrayFor(BaseDataType.WORD, tgt).elementType() shouldBe DataType.WORD
DataType.STR.elementType() shouldBe DataType.UBYTE
}
@@ -226,16 +240,31 @@ class TestDataType: FunSpec({
}
test("BaseDataType.largerSizeThan") {
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD).shouldBeTrue()
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE) shouldBe true
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD) shouldBe true
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD) shouldBe false
BaseDataType.STR.largerSizeThan(BaseDataType.UBYTE) shouldBe true
BaseDataType.STR.largerSizeThan(BaseDataType.BOOL) shouldBe true
BaseDataType.STR.largerSizeThan(BaseDataType.UWORD) shouldBe false
BaseDataType.STR.largerSizeThan(BaseDataType.WORD) shouldBe false
BaseDataType.STR.largerSizeThan(BaseDataType.POINTER) shouldBe false
BaseDataType.STR.largerSizeThan(BaseDataType.LONG) shouldBe false
BaseDataType.POINTER.largerSizeThan(BaseDataType.UBYTE) shouldBe true
BaseDataType.POINTER.largerSizeThan(BaseDataType.BOOL) shouldBe true
BaseDataType.POINTER.largerSizeThan(BaseDataType.UWORD) shouldBe false
BaseDataType.POINTER.largerSizeThan(BaseDataType.STR) shouldBe false
}
test("BaseDataType.equalsSize") {
BaseDataType.UBYTE.equalsSize(BaseDataType.UBYTE).shouldBeTrue()
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE).shouldBeTrue()
BaseDataType.WORD.equalsSize(BaseDataType.UWORD).shouldBeTrue()
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD).shouldBeFalse()
BaseDataType.UBYTE.equalsSize(BaseDataType.UBYTE) shouldBe true
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE) shouldBe true
BaseDataType.WORD.equalsSize(BaseDataType.UWORD) shouldBe true
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD) shouldBe false
BaseDataType.STR.equalsSize(BaseDataType.STR) shouldBe true
BaseDataType.STR.equalsSize(BaseDataType.UWORD) shouldBe true
BaseDataType.STR.equalsSize(BaseDataType.BYTE) shouldBe false
BaseDataType.POINTER.equalsSize(BaseDataType.UWORD) shouldBe true
BaseDataType.POINTER.equalsSize(BaseDataType.STR) shouldBe false
}
// ============================================================================
@@ -243,39 +272,39 @@ class TestDataType: FunSpec({
// ============================================================================
test("bool assignability") {
DataType.BOOL.isAssignableTo(DataType.BOOL).shouldBeTrue()
DataType.BOOL.isAssignableTo(DataType.UBYTE).shouldBeFalse()
DataType.BOOL.isAssignableTo(DataType.BOOL) shouldBe true
DataType.BOOL.isAssignableTo(DataType.UBYTE) shouldBe false
}
test("ubyte assignability") {
DataType.UBYTE.isAssignableTo(DataType.UBYTE).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.UWORD).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.FLOAT).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.UBYTE) shouldBe true
DataType.UBYTE.isAssignableTo(DataType.UWORD) shouldBe true
DataType.UBYTE.isAssignableTo(DataType.LONG) shouldBe true
DataType.UBYTE.isAssignableTo(DataType.FLOAT) shouldBe true
}
test("byte assignability") {
DataType.BYTE.isAssignableTo(DataType.BYTE).shouldBeTrue()
DataType.BYTE.isAssignableTo(DataType.WORD).shouldBeTrue()
DataType.BYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
DataType.BYTE.isAssignableTo(DataType.BYTE) shouldBe true
DataType.BYTE.isAssignableTo(DataType.WORD) shouldBe true
DataType.BYTE.isAssignableTo(DataType.LONG) shouldBe true
}
test("uword assignability") {
DataType.UWORD.isAssignableTo(DataType.UWORD).shouldBeTrue()
DataType.UWORD.isAssignableTo(DataType.LONG).shouldBeTrue()
DataType.UWORD.isAssignableTo(DataType.FLOAT).shouldBeTrue()
DataType.UWORD.isAssignableTo(DataType.UWORD) shouldBe true
DataType.UWORD.isAssignableTo(DataType.LONG) shouldBe true
DataType.UWORD.isAssignableTo(DataType.FLOAT) shouldBe true
}
test("string assignability") {
DataType.STR.isAssignableTo(DataType.STR).shouldBeTrue()
DataType.STR.isAssignableTo(DataType.UWORD).shouldBeTrue()
DataType.STR.isAssignableTo(DataType.STR) shouldBe true
DataType.STR.isAssignableTo(DataType.UWORD) shouldBe true
}
test("pointer assignability") {
val ptr = DataType.pointer(BaseDataType.UBYTE)
ptr.isAssignableTo(DataType.UWORD).shouldBeTrue()
ptr.isAssignableTo(DataType.LONG).shouldBeTrue()
ptr.isAssignableTo(DataType.pointer(BaseDataType.UBYTE)).shouldBeTrue()
ptr.isAssignableTo(DataType.UWORD) shouldBe true
ptr.isAssignableTo(DataType.LONG) shouldBe true
ptr.isAssignableTo(DataType.pointer(BaseDataType.UBYTE)) shouldBe true
}
// ============================================================================
@@ -290,7 +319,7 @@ class TestDataType: FunSpec({
}
test("DataType.toString for arrays") {
DataType.arrayFor(BaseDataType.UBYTE).toString() shouldBe "ubyte[]"
DataType.arrayFor(BaseDataType.WORD).toString() shouldBe "word[] (split)"
DataType.arrayFor(BaseDataType.UBYTE, tgt).toString() shouldBe "ubyte[]"
DataType.arrayFor(BaseDataType.WORD, tgt).toString() shouldBe "word[]"
}
})
@@ -256,16 +256,17 @@ class TestZeropageFreeList: FunSpec({
zp.availableBytes() shouldBe 0
}
test("BASICSAFE should have minimal locations") {
test("BASICSAFE should have minimal free locations") {
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val zp = PETZeropage(options)
// BASICSAFE has minimal locations (0xB1-0xBA per code, minus scratch locations)
zp.free.isNotEmpty().shouldBeTrue()
// After scratch removal, some locations in 0xb1-0xba range should still be free
// Just verify the free list is small but non-empty
zp.free.size shouldBeGreaterThan 0
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
// and $fb-$fe. After scratch removal, $fb-$fe remain usable.
zp.free.size shouldBeLessThan 20
zp.free.contains(0xfbu) shouldBe true
zp.free.contains(0xfcu) shouldBe true
zp.free.contains(0xfdu) shouldBe true
zp.free.contains(0xfeu) shouldBe true
verifyScratchLocationsReserved(zp)
}
@@ -279,6 +280,9 @@ class TestZeropageFreeList: FunSpec({
// FLOATSAFE and BASICSAFE should have same free locations for PET
floatZp.free.size shouldBe basicZp.free.size
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
floatZp.free.contains(addr) shouldBe true
}
}
test("KERNALSAFE should exclude IRQ locations") {
@@ -628,13 +632,17 @@ class TestZeropageFreeList: FunSpec({
}
}
test("BASICSAFE - minimal locations around 0xB1-0xBA") {
test("BASICSAFE - extra fb-fe free after scratch removal") {
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val zp = PETZeropage(options)
// BASICSAFE has minimal locations (0xB1-0xBA minus scratch)
zp.free.size shouldBeGreaterThan 0
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
// and $fb-$fe. After removing scratch registers from the pool,
// only $fb-$fe remain free.
zp.free.size shouldBeLessThan 15
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
zp.free.contains(addr) shouldBe true
}
}
test("KERNALSAFE - excludes IRQ locations") {
-1
View File
@@ -9,5 +9,4 @@ dependencies {
testImplementation(testFixtures(project(":codeCore")))
testImplementation("io.kotest:kotest-runner-junit5")
testImplementation("io.kotest:kotest-framework-datatest")
}
-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
File diff suppressed because it is too large. Load diff
@@ -1,9 +1,6 @@
package prog8.codegen.cpu6502
import prog8.code.GENERATED_LABEL_PREFIX
import prog8.code.StConstant
import prog8.code.StMemVar
import prog8.code.SymbolTable
import prog8.code.*
import prog8.code.core.ICompilationTarget
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
@@ -13,39 +10,51 @@ import prog8.code.core.ICompilationTarget
// TrimmedLine has .value (original), .trimmed (trimStart), and .instruction (instructionPart).
internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable): Int {
var numberOfOptimizations = 0
internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable) {
val pretrimmed = lines.map { it.trimStart() }.toMutableList()
/** Runs an optimization pass, applies modifications if any, and recomputes line windows. */
fun runPass(
mods: List<Modification>,
windowSize: Int,
currentLines: Sequence<List<TrimmedLine>>
): Sequence<List<TrimmedLine>> {
if (mods.isNotEmpty()) {
apply(mods, lines, pretrimmed)
numberOfOptimizations++
return getLinesBy(pretrimmed, lines, windowSize)
while(true) {
var modified = false
/** Runs a single optimization pass, applies modifications if any, and recomputes line windows. */
fun runPass(
mods: List<Modification>,
windowSize: Int,
currentLines: Sequence<List<TrimmedLine>>
): Sequence<List<TrimmedLine>> {
if (mods.isNotEmpty()) {
apply(mods, lines, pretrimmed)
modified = true
return getLinesBy(pretrimmed, lines, windowSize)
}
return currentLines
}
return currentLines
var linesByFour = getLinesBy(pretrimmed, lines, 4)
linesByFour = runPass(optimizeIncDec(linesByFour), 4, linesByFour)
linesByFour = runPass(optimizeStoreLoadSame(linesByFour, machine, symbolTable), 4, linesByFour)
linesByFour = runPass(optimizeJsrRtsAndOtherCombinations(linesByFour), 4, linesByFour)
linesByFour = runPass(optimizeUselessPushPopStack(linesByFour), 4, linesByFour)
linesByFour = runPass(optimizeUnneededTempvarInAdd(linesByFour), 4, linesByFour)
linesByFour = runPass(optimizeTSBtoRegularOr(linesByFour), 4, linesByFour)
var linesByFive = getLinesBy(pretrimmed, lines, 5)
linesByFive = runPass(optimizeFoldIndexOffset(linesByFive, symbolTable), 5, linesByFive)
var linesBySix = getLinesBy(pretrimmed, lines, 6)
linesBySix = runPass(optimizeFoldIndexOffsetSplitWord(linesBySix, symbolTable), 6, linesBySix)
var linesBySeven = getLinesBy(pretrimmed, lines, 7)
linesBySeven = runPass(optimizeFoldIndexOffsetNonSplitWord(linesBySeven, symbolTable), 7, linesBySeven)
var linesByFourteen = getLinesBy(pretrimmed, lines, 14)
linesByFourteen = runPass(optimizeSameAssignments(linesByFourteen, machine, symbolTable), 14, linesByFourteen)
linesByFourteen = runPass(optimizeSamePointerIndexingAndUselessBeq(linesByFourteen), 14, linesByFourteen)
linesByFourteen = runPass(optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen), 14, linesByFourteen)
if(!modified)
break
}
var linesByFour = getLinesBy(pretrimmed, lines, 4)
linesByFour = runPass(optimizeIncDec(linesByFour), 4, linesByFour)
linesByFour = runPass(optimizeStoreLoadSame(linesByFour, machine, symbolTable), 4, linesByFour)
linesByFour = runPass(optimizeJsrRtsAndOtherCombinations(linesByFour), 4, linesByFour)
linesByFour = runPass(optimizeUselessPushPopStack(linesByFour), 4, linesByFour)
linesByFour = runPass(optimizeUnneededTempvarInAdd(linesByFour), 4, linesByFour)
linesByFour = runPass(optimizeTSBtoRegularOr(linesByFour), 4, linesByFour)
var linesByFourteen = getLinesBy(pretrimmed, lines, 14)
linesByFourteen = runPass(optimizeSameAssignments(linesByFourteen, machine, symbolTable), 14, linesByFourteen)
linesByFourteen = runPass(optimizeSamePointerIndexingAndUselessBeq(linesByFourteen), 14, linesByFourteen)
linesByFourteen = runPass(optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen), 14, linesByFourteen)
return numberOfOptimizations
}
internal fun String.isBranch() = this.startsWith("b")
@@ -166,6 +175,18 @@ internal fun String.hasInstr(mnemonic: String): Boolean {
return false
}
private fun replaceMnemonic(raw: String, oldMnemonic: String, newMnemonic: String): String {
val trimmed = raw.trimStart()
val instruction = trimmed.instructionPart()
if(!instruction.startsWith(oldMnemonic))
return raw.replaceFirst(oldMnemonic, newMnemonic)
val instrStartInTrimmed = trimmed.indexOf(instruction)
if(instrStartInTrimmed<0) return raw.replaceFirst(oldMnemonic, newMnemonic)
val leadingLen = raw.length - trimmed.length
val idxInRaw = leadingLen + instrStartInTrimmed
return raw.substring(0, idxInRaw) + newMnemonic + raw.substring(idxInRaw + oldMnemonic.length)
}
/** Extracts the instruction part of a trimmed assembly line, skipping any label prefix.
* For "mylabel: lda #1" returns "lda #1". For "lda #1" returns "lda #1". */
private fun String.instructionPart(): String {
@@ -670,7 +691,8 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Trimme
}
else if (tfirst.hasInstr("jsr") && tsecond.hasInstr("rts")) {
if(!tfirst.contains("floats.pushFAC") && !tfirst.contains("floats.popFAC")) { // these 2 routines depend on being called with JSR!!
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
val replacement = replaceMnemonic(lines[0].value, "jsr", "jmp")
mods += Modification(lines[0].index, false, replacement)
mods += Modification(lines[1].index, true, null)
}
}
@@ -747,7 +769,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Trimme
val firstMnemonic = tfirst.take(3)
branchInversions[firstMnemonic]?.let { invertedBranch ->
if (sameLabel(tfirst, tsecond, tthird)) {
val branch = lines[1].value.replace("jmp", invertedBranch)
val branch = replaceMnemonic(lines[1].value, "jmp", invertedBranch)
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
@@ -873,6 +895,207 @@ private fun optimizeUnneededTempvarInAdd(linesByFour: Sequence<List<TrimmedLine>
return mods
}
private fun withLabel(line: TrimmedLine, newInstruction: String): String {
val label = keeplabelPretrimmed(line.value)
return if (label.isEmpty()) newInstruction else "$label ${newInstruction.trimStart()}"
}
private fun scaleConstant(constStr: String, scale: Int): String {
val value = if (constStr.startsWith("$"))
constStr.substring(1).toInt(16)
else
constStr.toInt()
val scaled = value * scale
return if (constStr.startsWith("$"))
"$${scaled.toString(16).padStart(2, '0')}"
else
scaled.toString()
}
private fun isArrayByteSizedAndSmall(name: String, symbolTable: SymbolTable): Boolean {
val symbol = symbolTable.flat[name]
return when (symbol) {
is StStaticVariable -> {
val len = symbol.length
(symbol.dt.isByteArray || symbol.dt.isString) && len != null && len <= 256u
}
is StMemVar -> {
val len = symbol.length
symbol.dt.isByteArray && len != null && len <= 256u
}
else -> false
}
}
private fun isArrayWordSizedAndSmall(name: String, symbolTable: SymbolTable): Boolean {
val symbol = symbolTable.flat[name]
return when (symbol) {
is StStaticVariable -> {
val len = symbol.length
symbol.dt.isWordArray && len != null && len <= 256u
}
is StMemVar -> {
val len = symbol.length
symbol.dt.isWordArray && len != null && len <= 256u
}
else -> false
}
}
private fun optimizeFoldIndexOffset(
linesByFive: Sequence<List<TrimmedLine>>,
symbolTable: SymbolTable
): List<Modification> {
val mods = mutableListOf<Modification>()
for (lines in linesByFive) {
val first = lines[0].instruction
val second = lines[1].instruction
val third = lines[2].instruction
val fourth = lines[3].instruction
val fifth = lines[4].instruction
if (!first.startsWith("lda ") || second != "clc" || !third.startsWith("adc "))
continue
if (fourth != "tay" && fourth != "tax")
continue
val reg = fourth[2]
val constOperand = third.extractOperandTrimmed()
if (!constOperand.startsWith("#"))
continue
val constValue = constOperand.substring(1)
val fifthOperand = fifth.extractOperandTrimmed()
if (!fifthOperand.endsWith(",$reg") || fifthOperand.startsWith("("))
continue
val addrPart = fifthOperand.dropLast(2)
if (!isArrayByteSizedAndSmall(addrPart, symbolTable)) continue
val mnemonic = fifth.take(3)
val varName = first.extractOperandTrimmed()
mods.add(Modification(lines[0].index, false, withLabel(lines[0], " ld$reg $varName")))
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[4].index, false, withLabel(lines[4], " $mnemonic $addrPart+$constValue,$reg")))
}
return mods
}
private fun optimizeFoldIndexOffsetSplitWord(
linesBySix: Sequence<List<TrimmedLine>>,
symbolTable: SymbolTable
): List<Modification> {
val mods = mutableListOf<Modification>()
for (lines in linesBySix) {
val first = lines[0].instruction
val second = lines[1].instruction
val third = lines[2].instruction
val fourth = lines[3].instruction
val fifth = lines[4].instruction
val sixth = lines[5].instruction
if (!first.startsWith("lda ") || second != "clc" || !third.startsWith("adc "))
continue
if (fourth != "tay" && fourth != "tax")
continue
val reg = fourth[2]
val constOperand = third.extractOperandTrimmed()
if (!constOperand.startsWith("#"))
continue
val constValue = constOperand.substring(1)
val fifthOp = fifth.extractOperandTrimmed()
val sixthOp = sixth.extractOperandTrimmed()
if (!fifthOp.endsWith(",$reg") || fifthOp.startsWith("("))
continue
if (!sixthOp.endsWith(",$reg") || sixthOp.startsWith("("))
continue
val lsbAddr = fifthOp.dropLast(2)
val msbAddr = sixthOp.dropLast(2)
if (!lsbAddr.endsWith("_lsb") || !msbAddr.endsWith("_msb"))
continue
val baseName = lsbAddr.dropLast(4)
if (msbAddr != baseName + "_msb")
continue
if (!isArrayWordSizedAndSmall(baseName, symbolTable))
continue
val lsbMnemonic = fifth.take(3)
val msbMnemonic = sixth.take(3)
val varName = first.extractOperandTrimmed()
mods.add(Modification(lines[0].index, false, withLabel(lines[0], " ld$reg $varName")))
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[4].index, false, withLabel(lines[4], " $lsbMnemonic $lsbAddr+$constValue,$reg")))
mods.add(Modification(lines[5].index, false, withLabel(lines[5], " $msbMnemonic $msbAddr+$constValue,$reg")))
}
return mods
}
private fun optimizeFoldIndexOffsetNonSplitWord(
linesBySeven: Sequence<List<TrimmedLine>>,
symbolTable: SymbolTable
): List<Modification> {
val mods = mutableListOf<Modification>()
for (lines in linesBySeven) {
val first = lines[0].instruction
val second = lines[1].instruction
val third = lines[2].instruction
val fourth = lines[3].instruction
val fifth = lines[4].instruction
val sixth = lines[5].instruction
val seventh = lines[6].instruction
if (!first.startsWith("lda ") || second != "clc" || !third.startsWith("adc "))
continue
if (fourth != "asl a" || (fifth != "tay" && fifth != "tax"))
continue
val reg = fifth[2]
val constOperand = third.extractOperandTrimmed()
if (!constOperand.startsWith("#"))
continue
val constValue = constOperand.substring(1)
val scaledConst = scaleConstant(constValue, 2)
val sixthOp = sixth.extractOperandTrimmed()
val seventhOp = seventh.extractOperandTrimmed()
if (!sixthOp.endsWith(",$reg") || sixthOp.startsWith("("))
continue
if (!seventhOp.endsWith(",$reg") || seventhOp.startsWith("("))
continue
val baseAddr = sixthOp.dropLast(2)
if (seventhOp != "$baseAddr+1,$reg")
continue
if (!isArrayWordSizedAndSmall(baseAddr, symbolTable))
continue
val lowMnemonic = sixth.take(3)
val highMnemonic = seventh.take(3)
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[5].index, false, withLabel(lines[5], " $lowMnemonic $baseAddr+$scaledConst,$reg")))
mods.add(Modification(lines[6].index, false, withLabel(lines[6], " $highMnemonic $baseAddr+1+$scaledConst,$reg")))
}
return mods
}
private fun optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen: Sequence<List<TrimmedLine>>): List<Modification> {
/*
; FIRST SEQUYENCE: P8ZP_SCRATCH_PTR += AY :
@@ -1,12 +1,14 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtSubroutineParameter
import prog8.code.core.CombinedLongRegisters
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub, argComplexity: List<Boolean>): List<Int> {
val order = mutableListOf<Int>()
// order is:
// 1) combined cx16 virtual registers (longs),
@@ -15,6 +17,8 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
// 4) single CPU registers (order Y,X,A),
// 5) floating point registers (FAC1, FAC2),
// 6) CPU Carry status flag
// Within each group, complex expressions are evaluated first (they may clobber registers,
// so running them before simple expressions avoids needing to save/restore already-loaded values).
val args = sub.parameters.withIndex()
val (combinedLongRegs, args1) = args.partition { it.value.first.registerOrPair in CombinedLongRegisters }
val (cx16regs, args2) = args1.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
@@ -23,12 +27,17 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
combinedLongRegs.forEach { order += it.index }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
combinedLongRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
cx16regs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
pairedRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
singleCpuRegs
.sortedWith(
compareByDescending<IndexedValue<Pair<RegisterOrStatusflag, PtSubroutineParameter>>> { argComplexity[it.index] }
.thenByDescending { it.value.first.registerOrPair!!.asCpuRegister() }
)
.forEach { order += it.index }
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
floatRegs.forEach { order += it.index }
floatRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
rest.forEach { order += it.index }
require(order.size==sub.parameters.size)
@@ -33,8 +33,8 @@ import prog8.codegen.cpu6502.assignment.*
return when (fcall.name) {
"msw" -> funcMsw(fcall, firstReturnRegister ?: RegisterOrPair.AY)
"lsw" -> funcLsw(fcall, firstReturnRegister ?: RegisterOrPair.AY)
"msb" -> funcMsb(fcall, firstReturnRegister ?: RegisterOrPair.A)
"msb__long" -> funcMsbLong(fcall, firstReturnRegister ?: RegisterOrPair.A)
"msb" -> funcMsb(fcall, false, firstReturnRegister ?: RegisterOrPair.A)
"msb__long" -> funcMsb(fcall, true, firstReturnRegister ?: RegisterOrPair.A)
"lsb" -> funcLsb(fcall, false, firstReturnRegister ?: RegisterOrPair.A)
"lsb__long" -> funcLsb(fcall,true, firstReturnRegister ?: RegisterOrPair.A)
"mkword" -> funcMkword(fcall, firstReturnRegister ?: RegisterOrPair.AY)
@@ -275,8 +275,14 @@ import prog8.codegen.cpu6502.assignment.*
asmgen.assignWordOperandsToAYAndVar(fcall.args[1], fcall.args[0], "P8ZP_SCRATCH_W1")
// math.divmod_uw_asm: -- divide two unsigned words (16 bit each) into 16 bit results
// input: P8ZP_SCRATCH_W1 in ZP: 16-bit number, A/Y: 16 bit divisor
// output: cx16.r15: 16-bit remainder, A/Y: 16 bit division result
// output: P8ZP_SCRATCH_W2: 16-bit remainder, A/Y: 16 bit division result
asmgen.out(" jsr prog8_math.divmod_uw_asm")
// copy remainder from P8ZP_SCRATCH_W2 to R15 (needed as second return value)
asmgen.out("""
ldx P8ZP_SCRATCH_W2
stx cx16.r15L
ldx P8ZP_SCRATCH_W2+1
stx cx16.r15H""".trimIndent())
return arrayOf(RegisterOrPair.AY, RegisterOrPair.R15)
}
@@ -370,6 +376,11 @@ import prog8.codegen.cpu6502.assignment.*
val identifier = fcall.args[0] as? PtIdentifier
if(identifier!=null) {
// NOTE: `jmp (ptr)` below has the 6502 page-wrap bug on plain
// 6502 if the pointer (the identifier, a variable holding a
// routine address) lands at $xxFF. 65C02 is fine. Latent; only
// misbehaves for variables placed at a $xxFF address. The
// new6502 codegen's CALLI has the same hazard.
asmgen.out("""
; push a return address so the jmp becomes indirect jsr
lda #>((+)-1)
@@ -381,6 +392,9 @@ import prog8.codegen.cpu6502.assignment.*
return arrayOf(RegisterOrPair.AY)
}
// NOTE: same 6502 page-wrap pitfall as above: the pointer is
// staged through P8ZP_SCRATCH_W2 before the indirect jmp, so the
// scratch word must not land at $xxFF on plain 6502. 65C02 is fine.
asmgen.assignExpressionToVariable(fcall.args[0], asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD) // jump address
asmgen.out("""
; push a return address so the jmp becomes indirect jsr
@@ -396,14 +410,11 @@ import prog8.codegen.cpu6502.assignment.*
private fun funcCallFar(fcall: PtFunctionCall): Array<RegisterOrPair> {
val targetName = asmgen.options.compTarget.name
if(targetName !in arrayOf("cx16", "c64", "c128"))
throw AssemblyError("callfar only works on cx16, c64 and c128 targets at this time")
val jsrfar = when(targetName) {
"cx16" -> "cx16.JSRFAR"
"c64" -> "c64.x16jsrfar"
"c128" -> "c128.x16jsrfar"
else -> TODO("jsrfar routine")
else -> "$targetName.x16jsrfar"
}
val constBank = fcall.args[0].asConstInteger()
val constAddress = fcall.args[1].asConstInteger()
@@ -494,83 +505,115 @@ import prog8.codegen.cpu6502.assignment.*
val arg2 = fcall.args[1]
if(arg1.type.isByte) {
if(arg2.type.isByte) {
val op = if (arg1.type.isSigned || arg2.type.isSigned) "sec | sbc" else "cmp"
when (arg2) {
is PtIdentifier -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
asmgen.out(" $op ${asmgen.asmVariableName(arg2)}")
}
is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.number.toInt()}")
asmgen.out(" $op #${arg2.number.toInt()}")
}
is PtBool -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.asInt()}")
asmgen.out(" $op #${arg2.asInt()}")
}
is PtMemoryByte -> {
if(arg2.address is PtNumber) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.address.asConstInteger()!!.toHex()}")
asmgen.out(" $op ${arg2.address.asConstInteger()!!.toHex()}")
} else {
asmgen.assignByteOperandsToAAndVar(arg1, arg2, "P8ZP_SCRATCH_B1")
asmgen.out(" cmp P8ZP_SCRATCH_B1")
asmgen.out(" $op P8ZP_SCRATCH_B1")
}
}
else -> {
asmgen.assignByteOperandsToAAndVar(arg1, arg2, "P8ZP_SCRATCH_B1")
asmgen.out(" cmp P8ZP_SCRATCH_B1")
asmgen.out(" $op P8ZP_SCRATCH_B1")
}
}
} else
throw AssemblyError("args for cmp() should have same dt")
} else if(arg1.type.isWord) {
if(arg2.type.isWord) {
if(arg1.type.isSigned) {
if(arg1.type.isSigned || arg2.type.isSigned) {
when (arg2) {
is PtIdentifier -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
val var2 = asmgen.asmVariableName(arg2)
asmgen.out("""
sec
sbc ${asmgen.asmVariableName(arg2)}
sbc $var2
sta P8ZP_SCRATCH_B1
tya
sbc ${asmgen.asmVariableName(arg2)}+1""")
sbc $var2+1
php
bne +
lda P8ZP_SCRATCH_B1
beq +
pla
and #%11111101
pha
+ plp""")
}
is PtBool -> TODO("word compare against bool ${arg2.position}")
is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
val num = arg2.number.toInt()
asmgen.out("""
sec
sbc #<${arg2.number.toInt()}
sbc #<($num)
sta P8ZP_SCRATCH_B1
tya
sbc #>${arg2.number.toInt()}""")
sbc #>($num)
php
bne +
lda P8ZP_SCRATCH_B1
beq +
pla
and #%11111101
pha
+ plp""")
}
else -> {
asmgen.assignWordOperandsToAYAndVar(arg1, arg2, "P8ZP_SCRATCH_W1")
asmgen.out("""
sec
sbc P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_B1
tya
sbc P8ZP_SCRATCH_W1+1""")
sbc P8ZP_SCRATCH_W1+1
php
bne +
lda P8ZP_SCRATCH_B1
beq +
pla
and #%11111101
pha
+ plp""")
}
}
} else {
when (arg2) {
is PtIdentifier -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
val var2 = asmgen.asmVariableName(arg2)
asmgen.out("""
cpy ${asmgen.asmVariableName(arg2)}+1
cpy $var2+1
bne +
cmp ${asmgen.asmVariableName(arg2)}
+""")
cmp $var2
+""")
}
is PtBool -> TODO("word compare against bool ${arg2.position}")
is PtNumber -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
val num = arg2.number.toInt()
asmgen.out("""
cpy #>${arg2.number.toInt()}
cpy #>($num)
bne +
cmp #<${arg2.number.toInt()}
+""")
cmp #<($num)
+""")
}
else -> {
asmgen.assignWordOperandsToAYAndVar(arg1, arg2, "P8ZP_SCRATCH_W1")
@@ -578,7 +621,7 @@ import prog8.codegen.cpu6502.assignment.*
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
+""")
}
}
}
@@ -593,12 +636,25 @@ import prog8.codegen.cpu6502.assignment.*
sec
lda $var1
sbc #$${hex.substring(6, 8)}
sta P8ZP_SCRATCH_B1
lda $var1+1
sbc #$${hex.substring(4, 6)}
ora P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_B1
lda $var1+2
sbc #$${hex.substring(2, 4)}
ora P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_B1
lda $var1+3
sbc #$${hex.take(2)}""")
sbc #$${hex.take(2)}
php
bne +
lda P8ZP_SCRATCH_B1
beq +
pla
and #%11111101
pha
+ plp""")
} else if(arg1 is PtIdentifier && arg2 is PtIdentifier) {
val var1 = asmgen.asmVariableName(arg1)
val var2 = asmgen.asmVariableName(arg2)
@@ -606,26 +662,55 @@ import prog8.codegen.cpu6502.assignment.*
sec
lda $var1
sbc $var2
sta P8ZP_SCRATCH_B1
lda $var1+1
sbc $var2+1
ora P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_B1
lda $var1+2
sbc $var2+2
ora P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_B1
lda $var1+3
sbc $var2+3""")
sbc $var2+3
php
bne +
lda P8ZP_SCRATCH_B1
beq +
pla
and #%11111101
pha
+ plp""")
} else {
// cmp() doesn't return a value and as such can't be used in an expression, so no need to save the temp registers' original values
assignAsmGen.assignExpressionToRegister(arg2, RegisterOrPair.R14R15, true)
if(!assignAsmGen.isRightTrivial(arg1))
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
assignAsmGen.assignExpressionToRegister(arg1, RegisterOrPair.R12R13, true)
if(!assignAsmGen.isRightTrivial(arg1))
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
asmgen.out("""
sec
lda cx16.r12
sbc cx16.r14
sta P8ZP_SCRATCH_B1
lda cx16.r12+1
sbc cx16.r14+1
ora P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_B1
lda cx16.r12+2
sbc cx16.r14+2
ora P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_B1
lda cx16.r12+3
sbc cx16.r14+3""")
sbc cx16.r14+3
php
bne +
lda P8ZP_SCRATCH_B1
beq +
pla
and #%11111101
pha
+ plp""")
}
} else
throw AssemblyError("args for cmp() should have same dt")
@@ -683,7 +768,7 @@ import prog8.codegen.cpu6502.assignment.*
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("ptr indexing ${what.position}")
TODO("ror2 byte ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable!!)
@@ -709,7 +794,7 @@ import prog8.codegen.cpu6502.assignment.*
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("ptr indexing ${what.position}")
TODO("ror2 word ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
@@ -761,7 +846,7 @@ import prog8.codegen.cpu6502.assignment.*
if(!what.index.isSimple()) asmgen.out(" plp")
if(what.variable==null)
TODO("ptr indexing ${what.position}")
TODO("ror byte ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" ror ${varname},x")
}
@@ -803,7 +888,7 @@ import prog8.codegen.cpu6502.assignment.*
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
if(what.variable==null)
TODO("ptr indexing ${what.position}")
TODO("ror word ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" ror ${varname}_msb,x | ror ${varname}_lsb,x")
@@ -870,7 +955,7 @@ import prog8.codegen.cpu6502.assignment.*
when (what) {
is PtArrayIndexer -> {
if(what.variable==null)
TODO("ptr indexing ${what.position}")
TODO("rol2 byte ptr indexing ${what.position}")
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
@@ -896,7 +981,7 @@ import prog8.codegen.cpu6502.assignment.*
is PtArrayIndexer -> {
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(what.variable==null)
TODO("ptr indexing ${what.position}")
TODO("rol2 word ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" asl ${varname}_lsb,x | rol ${varname}_msb,x | bcc + | inc ${varname}_lsb,x |+")
@@ -944,7 +1029,7 @@ import prog8.codegen.cpu6502.assignment.*
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
if(what.variable==null)
TODO("ptr indexing ${what.position}")
TODO("rol byte ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
asmgen.out(" rol ${varname},x")
}
@@ -986,7 +1071,7 @@ import prog8.codegen.cpu6502.assignment.*
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
if(!what.index.isSimple()) asmgen.out(" plp")
if(what.variable==null)
TODO("ptr indexing ${what.position}")
TODO("rol word ptr indexing ${what.position}")
val varname = asmgen.asmVariableName(what.variable!!)
if(what.splitWords)
asmgen.out(" rol ${varname}_lsb,x | rol ${varname}_msb,x")
@@ -1060,7 +1145,7 @@ import prog8.codegen.cpu6502.assignment.*
val msbAdd: Int
if(indexer.splitWords) {
val arrayVariable = indexer.variable ?: TODO("ptr indexing ${indexer.position}")
indexer.setChild(0, PtIdentifier(arrayVariable.name + (if(msb) "_msb" else "_lsb"), DataType.arrayFor(BaseDataType.UBYTE, false), arrayVariable.position))
indexer.setChild(0, PtIdentifier(arrayVariable.name + (if(msb) "_msb" else "_lsb"), DataType.arrayFor(BaseDataType.UBYTE, asmgen.options.compTarget), arrayVariable.position))
elementSize = 1
msbAdd = 0
} else {
@@ -1117,20 +1202,17 @@ import prog8.codegen.cpu6502.assignment.*
val arg = fcall.args.single()
if(arg is PtIdentifier) {
val varname = asmgen.asmVariableName(arg)
when(val dt = arg.type.base) {
BaseDataType.UBYTE -> asmgen.out(" lda $varname | jsr prog8_lib.func_sign_ub_into_A")
BaseDataType.BYTE -> asmgen.out(" lda $varname | jsr prog8_lib.func_sign_b_into_A")
BaseDataType.UWORD -> asmgen.out(" lda $varname+1 | jsr prog8_lib.func_sign_ub_into_A")
BaseDataType.WORD -> asmgen.out(" lda $varname+1 | jsr prog8_lib.func_sign_b_into_A")
BaseDataType.LONG -> asmgen.out(" lda $varname+3 | jsr prog8_lib.func_sign_b_into_A")
BaseDataType.FLOAT -> {
translateArguments(fcall, null, scope)
asmgen.out(" jsr floats.func_sign_f_into_A")
when(arg.type.base) {
BaseDataType.UBYTE -> {
asmgen.out(" lda $varname | jsr prog8_lib.func_sign_ub_into_A")
return arrayOf(RegisterOrPair.A)
}
else -> throw AssemblyError("weird type $dt")
BaseDataType.BYTE -> {
asmgen.out(" lda $varname | jsr prog8_lib.func_sign_b_into_A")
return arrayOf(RegisterOrPair.A)
}
else -> {}
}
// result in A
return arrayOf(RegisterOrPair.A)
}
when (val dt = arg.type.base) {
@@ -1358,7 +1440,7 @@ import prog8.codegen.cpu6502.assignment.*
return emptyArray()
} else if(addressOfIdentifier!=null && (addressOfIdentifier.type.isWord || addressOfIdentifier.type.isPointer || addressOfIdentifier.type.isByteArray)) {
var varname = asmgen.asmVariableName(addressOfIdentifier)
if(addressOfIdentifier.type.isSplitWordArray) {
if(addressOfIdentifier.type.isSplitWordArray(asmgen.options.compTarget)) {
val addrOf = result.first as PtAddressOf
varname = if(addrOf.isMsbForSplitArray) varname+"_msb" else varname+"_lsb"
}
@@ -1740,7 +1822,7 @@ import prog8.codegen.cpu6502.assignment.*
}
} else if(addressOfIdentifier!=null && (addressOfIdentifier.type.isWord || addressOfIdentifier.type.isPointer || addressOfIdentifier.type.isByteArray)) {
var varname = asmgen.asmVariableName(addressOfIdentifier)
if(addressOfIdentifier.type.isSplitWordArray) {
if(addressOfIdentifier.type.isSplitWordArray(asmgen.options.compTarget)) {
val addrOf = result.first as PtAddressOf
varname = if(addrOf.isMsbForSplitArray) varname+"_msb" else varname+"_lsb"
}
@@ -1846,7 +1928,7 @@ import prog8.codegen.cpu6502.assignment.*
if(tryOptimizedLoopCopyWithY("", "$address"))
return emptyArray()
asmgen.assignVariableToRegister(address.toString(), RegisterOrPair.R14R15, null, fcall.position)
asmgen.assignVariableToRegister(address.toString(), RegisterOrPair.R14R15, null, fcall.position, true)
return arrayOf(RegisterOrPair.R14R15)
}
@@ -1875,7 +1957,7 @@ import prog8.codegen.cpu6502.assignment.*
if(tryOptimizedLoopCopyWithY("lda #<$varname+$offset | sta P8ZP_SCRATCH_W1 | lda #>$varname+$offset | sta P8ZP_SCRATCH_W1+1", "(P8ZP_SCRATCH_W1)"))
return emptyArray()
asmgen.assignVariableToRegister("$varname+$offset", RegisterOrPair.R14R15, null, fcall.position)
asmgen.assignVariableToRegister("$varname+$offset", RegisterOrPair.R14R15, null, fcall.position, true)
return arrayOf(RegisterOrPair.R14R15)
} else if(result.second is PtIdentifier) {
val offsetname = asmgen.asmVariableName(result.second as PtIdentifier)
@@ -1899,27 +1981,155 @@ import prog8.codegen.cpu6502.assignment.*
return arrayOf(RegisterOrPair.R14R15)
}
private fun inlineClampByte(value: PtExpression, minVal: Int, maxVal: Int, signed: Boolean) {
val typeMin = if (signed) -128 else 0
val typeMax = if (signed) 127 else 255
assignAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, signed)
when {
minVal == typeMin && maxVal == typeMax -> {
// no-op: the value in A is already the clamped result
}
minVal == typeMin -> {
// upper clamp only: min(x, maxVal)
emitByteUpperClamp(maxVal, signed)
}
maxVal == typeMax -> {
// lower clamp only: max(x, minVal)
emitByteLowerClamp(minVal, signed)
}
else -> {
// two-sided clamp: upper then lower, sequentially on the running value in A
emitByteUpperClamp(maxVal, signed)
emitByteLowerClamp(minVal, signed)
}
}
}
private fun emitByteUpperClamp(maxVal: Int, signed: Boolean) {
// result = min(x, maxVal); x is in A on entry
// mirrors the proven func_clamp_byte sequence (sec/sbc + bmi means "x < max")
val imm = "#${(maxVal and 0xff).toHex()}"
if (signed) {
val keep = asmgen.makeLabel("clampkeep")
val done = asmgen.makeLabel("clampdone")
asmgen.out(listOf(
" tay",
" sec",
" sbc $imm",
" bvc +",
" eor #128",
"+ bmi $keep",
" lda $imm",
" jmp $done",
"$keep tya",
done
).joinToString("\n"))
} else {
val keep = asmgen.makeLabel("clampkeep")
asmgen.out(listOf(
" cmp $imm",
" bcc $keep",
" lda $imm",
keep
).joinToString("\n"))
}
}
private fun emitByteLowerClamp(minVal: Int, signed: Boolean) {
// result = max(x, minVal); x is in A on entry
// mirrors the proven func_clamp_byte sequence (sec/sbc + bpl means "x >= min")
val imm = "#${(minVal and 0xff).toHex()}"
if (signed) {
val keep = asmgen.makeLabel("clampkeep")
val done = asmgen.makeLabel("clampdone")
asmgen.out(listOf(
" tay",
" sec",
" sbc $imm",
" bvc +",
" eor #128",
"+ bpl $keep",
" lda $imm",
" jmp $done",
"$keep tya",
done
).joinToString("\n"))
} else {
val keep = asmgen.makeLabel("clampkeep")
asmgen.out(listOf(
" cmp $imm",
" bcs $keep",
" lda $imm",
keep
).joinToString("\n"))
}
}
private fun funcClamp(fcall: PtFunctionCall): Array<RegisterOrPair> {
val signed = fcall.type.isSigned
val zpCheck = { name: String -> asmgen.isZpVar(name) }
when {
fcall.type.isByte -> {
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
if (!signed && fcall.args[1] is PtNumber && fcall.args[2] is PtNumber) {
// unsigned (ubyte) clamp with constant bounds: emit a small inline clamp.
// Only worthwhile for ubyte: the signed inline sequence is larger than the
// shared jsr func_clamp_byte subroutine, so signed clamps keep using that.
val minVal = (fcall.args[1] as PtNumber).number.toInt()
val maxVal = (fcall.args[2] as PtNumber).number.toInt()
inlineClampByte(fcall.args[0], minVal, maxVal, signed)
return arrayOf(RegisterOrPair.A)
}
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck) && fcall.args[2].isSimple(zpCheck)) {
// simple values (number/variable) do not clobber W1, so min/max can be evaluated first
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value
} else {
// any non-simple argument may use W1 as a temporary, so evaluate value and minimum
// first and preserve them while the maximum is evaluated
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed) // value first (may use W1 as temp)
asmgen.out(" pha")
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha")
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W1+1", fcall.args[2].type) // maximum (may clobber W1)
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1")
asmgen.out(" pla")
}
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}") // result in A
return arrayOf(RegisterOrPair.A)
}
fcall.type.isWord -> {
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck) && fcall.args[2].isSimple(zpCheck)) {
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
} else {
// any non-simple argument may use P8ZP_SCRATCH_W1/W2 as temporaries (e.g. via multiply_words/divmod_w_asm),
// so evaluate value and minimum first and preserve them while the maximum is evaluated
assignAsmGen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed) // value
asmgen.out(" pha\n tya\n pha") // save AY on stack (A low, Y high)
assignAsmGen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W1", fcall.args[1].type) // minimum
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha\n lda P8ZP_SCRATCH_W1+1\n pha") // save minimum
assignAsmGen.assignExpressionToVariable(fcall.args[2], "P8ZP_SCRATCH_W2", fcall.args[2].type) // maximum (may clobber W1)
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1+1\n pla\n sta P8ZP_SCRATCH_W1") // restore minimum
asmgen.out(" pla\n tay\n pla") // restore AY
}
asmgen.out(" jsr prog8_lib.func_clamp_${fcall.type.toString().lowercase()}") // result in AY
return arrayOf(RegisterOrPair.AY)
}
fcall.type.isLong -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R10R11, true) // low
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R12R13, true) // high
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true) // val
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck) && fcall.args[2].isSimple(zpCheck)) {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R10R11, true) // low
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R12R13, true) // high
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true) // val
} else {
// same ordering issue: value may clobber R10R11/R12R13 via multiply_longs/div_longs
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R14R15, true) // val first
asmgen.out(" lda cx16.r14\n pha\n lda cx16.r14+1\n pha\n lda cx16.r15\n pha\n lda cx16.r15+1\n pha")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R10R11, true) // low
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.R12R13, true) // high
asmgen.out(" pla\n sta cx16.r15+1\n pla\n sta cx16.r15\n pla\n sta cx16.r14+1\n pla\n sta cx16.r14")
}
asmgen.out(" jsr prog8_lib.func_clamp_long")
return arrayOf(RegisterOrPair.R14R15)
}
@@ -1929,11 +2139,20 @@ import prog8.codegen.cpu6502.assignment.*
private fun funcMin(fcall: PtFunctionCall): Array<RegisterOrPair> {
val signed = fcall.type.isSigned
val zpCheck = { name: String -> asmgen.isZpVar(name) }
when {
fcall.type.isByte -> {
if (signed) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
} else {
// left or right may use B1/REG as temp (e.g. multiply), so save left
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right (may clobber B1)
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
}
asmgen.out("""
lda P8ZP_SCRATCH_B1
sec
@@ -1946,8 +2165,18 @@ import prog8.codegen.cpu6502.assignment.*
+ lda P8ZP_SCRATCH_B1
+""")
} else {
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left
} else {
// right in B1, left in A - left may use B1 as temp, so save right
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_B1", fcall.type) // right
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // left (may clobber B1)
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
// now A holds left, need to compare again? Actually left already in A, right in B1 restored, so cmp is still valid
// Re-do cmp after restore is not needed, the following cmp uses P8ZP_SCRATCH_B1 correctly
}
asmgen.out(" cmp P8ZP_SCRATCH_B1")
asmgen.out(" bcc +")
asmgen.out("""
@@ -1957,8 +2186,15 @@ import prog8.codegen.cpu6502.assignment.*
return arrayOf(RegisterOrPair.A)
}
fcall.type.isWord -> {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
} else {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha\n lda P8ZP_SCRATCH_W1+1\n pha")
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right (may clobber W1)
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1+1\n pla\n sta P8ZP_SCRATCH_W1")
}
if(signed) {
asmgen.out("""
lda P8ZP_SCRATCH_W1
@@ -1996,7 +2232,11 @@ import prog8.codegen.cpu6502.assignment.*
}
fcall.type.isLong -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R12R13, true) // left
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R14R15, true) // right
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
asmgen.out("""
lda cx16.r12
cmp cx16.r14
@@ -2028,11 +2268,19 @@ import prog8.codegen.cpu6502.assignment.*
private fun funcMax(fcall: PtFunctionCall): Array<RegisterOrPair> {
val signed = fcall.type.isSigned
val zpCheck = { name: String -> asmgen.isZpVar(name) }
when {
fcall.type.isByte -> {
if (signed) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right
} else {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_REG", fcall.type) // right (may clobber B1)
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
}
asmgen.out("""
lda P8ZP_SCRATCH_B1
sec
@@ -2045,8 +2293,15 @@ import prog8.codegen.cpu6502.assignment.*
+ lda P8ZP_SCRATCH_B1
+""")
} else {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right
} else {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_B1", fcall.type) // left
asmgen.out(" lda P8ZP_SCRATCH_B1\n pha")
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // right (may clobber B1)
asmgen.out(" pla\n sta P8ZP_SCRATCH_B1")
}
asmgen.out(" cmp P8ZP_SCRATCH_B1")
asmgen.out(" bcs +")
asmgen.out("""
@@ -2056,8 +2311,15 @@ import prog8.codegen.cpu6502.assignment.*
return arrayOf(RegisterOrPair.A)
}
fcall.type.isWord -> {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
if (fcall.args[0].isSimple(zpCheck) && fcall.args[1].isSimple(zpCheck)) {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right
} else {
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", fcall.type) // left
asmgen.out(" lda P8ZP_SCRATCH_W1\n pha\n lda P8ZP_SCRATCH_W1+1\n pha")
asmgen.assignExpressionToVariable(fcall.args[1], "P8ZP_SCRATCH_W2", fcall.type) // right (may clobber W1)
asmgen.out(" pla\n sta P8ZP_SCRATCH_W1+1\n pla\n sta P8ZP_SCRATCH_W1")
}
if (signed) {
asmgen.out("""
lda P8ZP_SCRATCH_W1
@@ -2095,7 +2357,11 @@ import prog8.codegen.cpu6502.assignment.*
}
fcall.type.isLong -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R12R13, true) // left
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R14R15, true) // right
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
asmgen.out("""
lda cx16.r12
cmp cx16.r14
@@ -2261,55 +2527,36 @@ import prog8.codegen.cpu6502.assignment.*
return arrayOf(RegisterOrPair.AY)
}
private fun funcMsbLong(fcall: PtFunctionCall, resultReg: RegisterOrPair): Array<RegisterOrPair> {
private fun funcMsb(fcall: PtFunctionCall, fromLong: Boolean, resultReg: RegisterOrPair): Array<RegisterOrPair> {
val arg = fcall.args.single()
if (!arg.type.isLong)
throw AssemblyError("msb__long requires long argument")
if (arg is PtNumber)
throw AssemblyError("msb(const) should have been const-folded away")
if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg)
when(resultReg) {
RegisterOrPair.A -> asmgen.out(" lda $sourceName+3")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+3")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+3")
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
}
} else if(arg is PtArrayIndexer) {
TODO("msb of long array element ${fcall.position}")
if(fromLong) {
if (!arg.type.isLong)
throw AssemblyError("msb__long requires long argument")
} else {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.R14R15, arg.type.isSigned)
when(resultReg) {
RegisterOrPair.A -> asmgen.out(" lda cx16.r14+3")
RegisterOrPair.X -> asmgen.out(" ldx cx16.r14+3")
RegisterOrPair.Y -> asmgen.out(" ldy cx16.r14+3")
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
}
if (!arg.type.isWord && !arg.type.isPointer)
throw AssemblyError("msb requires word argument")
}
return arrayOf(resultReg)
}
private fun funcMsb(fcall: PtFunctionCall, resultReg: RegisterOrPair): Array<RegisterOrPair> {
val arg = fcall.args.single()
if (!arg.type.isWord)
throw AssemblyError("msb requires word argument")
if (arg is PtNumber)
throw AssemblyError("msb(const) should have been const-folded away")
val offset = if(fromLong) 3 else 1
if (arg is PtIdentifier) {
val sourceName = asmgen.asmVariableName(arg)
when(resultReg) {
RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+1 | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+1 | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
RegisterOrPair.A -> asmgen.out(" lda $sourceName+$offset")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+$offset")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+$offset")
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+$offset | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+$offset | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+$offset | ldy #0")
else -> throw AssemblyError("invalid register for msb${if(fromLong) " long" else ""}: $resultReg ${arg.position}")
}
} else {
if(arg is PtArrayIndexer) {
if(fromLong)
TODO("msb of long array element ${fcall.position}")
// just read the msb byte out of the word array
if(arg.splitWords) {
if(arg.variable==null)
@@ -2331,15 +2578,15 @@ import prog8.codegen.cpu6502.assignment.*
RegisterOrPair.AX -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" lda $arrayVar,y | ldx #0")
}
}
RegisterOrPair.AY -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.Y)
asmgen.out(" lda $arrayVar,y | ldy #0")
}
}
RegisterOrPair.XY -> {
asmgen.loadScaledArrayIndexIntoRegister(arg, CpuRegister.X)
asmgen.out(" ldy $arrayVar,x | ldx #0")
}
}
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
}
} else {
@@ -2375,6 +2622,14 @@ import prog8.codegen.cpu6502.assignment.*
else -> throw AssemblyError("invalid register for msb: $resultReg ${arg.position}")
}
}
} else if(fromLong) {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.R14R15, arg.type.isSigned)
when(resultReg) {
RegisterOrPair.A -> asmgen.out(" lda cx16.r14+$offset")
RegisterOrPair.X -> asmgen.out(" ldx cx16.r14+$offset")
RegisterOrPair.Y -> asmgen.out(" ldy cx16.r14+$offset")
else -> throw AssemblyError("invalid register for msb long: $resultReg ${arg.position}")
}
} else {
asmgen.assignExpressionToRegister(arg, RegisterOrPair.AY)
asmgen.out(" tya")
@@ -2389,7 +2644,7 @@ import prog8.codegen.cpu6502.assignment.*
if(fromLong) {
if (!arg.type.isLong) throw AssemblyError("lsb__long requires long")
} else {
if (!arg.type.isWord) throw AssemblyError("lsb requires word")
if (!arg.type.isWord && !arg.type.isPointer) throw AssemblyError("lsb requires word")
}
if (arg is PtNumber)
throw AssemblyError("lsb(const) should have been const-folded away")
@@ -2515,7 +2770,7 @@ import prog8.codegen.cpu6502.assignment.*
private fun translateArguments(call: PtFunctionCall, funcname: String?, scope: IPtSubroutine?) {
val signature = BuiltinFunctions.getValue(call.name)
val callConv = signature.callConvention(call.args.map {
val callConv = signature.callConventionFor6502(call.args.map {
require(it.type.isNumericOrBool)
it.type.base
})
@@ -2568,7 +2823,7 @@ import prog8.codegen.cpu6502.assignment.*
conv.dt==BaseDataType.LONG -> getSourceForLong(value)
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false, value.position)
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForUntypedAddressOf(false, program.memsizer), false, value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
@@ -2587,7 +2842,7 @@ import prog8.codegen.cpu6502.assignment.*
conv.dt==BaseDataType.LONG -> AsmAssignSource.fromAstSource(value, program, asmgen)
conv.dt.isPassByRef -> {
// put the address of the argument in AY
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForAddressOf(false), false,value.position)
val addr = PtAddressOf(DataType.forDt(conv.dt).typeForUntypedAddressOf(false, program.memsizer), false,value.position)
addr.add(value)
addr.parent = call
AsmAssignSource.fromAstSource(addr, program, asmgen)
@@ -5,6 +5,7 @@ import prog8.code.StMemVar
import prog8.code.StStaticVariable
import prog8.code.ast.PtForLoop
import prog8.code.ast.PtIdentifier
import prog8.code.ast.PtNumber
import prog8.code.ast.PtRange
import prog8.code.core.*
import kotlin.math.absoluteValue
@@ -33,21 +34,502 @@ internal class ForLoopsAsmGen(
}
private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
if(range.step.asConstInteger()!! < -1) {
val constStep = range.step.asConstInteger()
if(constStep != null && constStep < -1) {
val limit = range.to.asConstInteger()
if(limit==0)
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
}
if(range.step !is PtNumber) {
translateForOverNonconstStepRange(stmt, iterableDt, range)
return
}
when {
iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range)
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range)
else -> throw AssemblyError("range expression can only be byte or word")
iterableDt.isWordArray && !iterableDt.isSplitWordArray(asmgen.options.compTarget) -> forOverNonconstWordRange(stmt, iterableDt, range)
iterableDt.isLongArray -> forOverNonconstLongRange(stmt, iterableDt, range)
else -> throw AssemblyError("range expression can only be byte, word or long")
}
asmgen.loopEndLabels.removeLast()
}
private fun translateForOverNonconstStepRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
when {
iterableDt.isByteArray -> forOverNonconstStepByteRange(stmt, iterableDt, range)
iterableDt.isWordArray && !iterableDt.isSplitWordArray(asmgen.options.compTarget) -> forOverNonconstStepWordRange(stmt, iterableDt, range)
iterableDt.isLongArray -> throw AssemblyError("variable-step for loops over long ranges are not supported by the legacy 6502 code generator at ${stmt.position}")
else -> throw AssemblyError("range expression can only be byte, word or long")
}
asmgen.loopEndLabels.removeLast()
}
private fun forOverNonconstStepByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val loopDt = iterableDt.elementType()
val loopvar = asmgen.asmVariableName(stmt.variable)
val toValueVar = asmgen.createTempVarReused(loopDt.base, false, range)
val stepVar = asmgen.createTempVarReused(range.step.type.base, false, range)
val nextVar = asmgen.createTempVarReused(loopDt.base, false, range)
val loopLabel = asmgen.makeLabel("for_loop")
val descendingLabel = if(range.step.type.isSigned) asmgen.makeLabel("for_desc") else null
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
// Keep range expression evaluation in source order and preserve each value across the next one.
asmgen.assignExpressionToVariable(range.from, loopvar, loopDt)
asmgen.assignExpressionToVariable(range.to, toValueVar, loopDt)
asmgen.assignExpressionToVariable(range.step, stepVar, range.step.type)
if (range.step.type.isSigned)
asmgen.out(" lda $stepVar | beq $endLabel | bmi $descendingLabel")
else
asmgen.out(" lda $stepVar | beq $endLabel")
emitByteRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, true)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
emitByteRangeUpdate(loopvar, toValueVar, stepVar, nextVar, endLabel, loopLabel, loopDt.isSigned, range.step.type.isSigned)
if(descendingLabel != null) {
asmgen.out(descendingLabel)
emitByteRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, false)
asmgen.out(" jmp $loopLabel")
}
asmgen.out(endLabel)
}
private fun emitByteRangePrecheck(loopvar: String, toValueVar: String, loopLabel: String, endLabel: String, signed: Boolean, ascending: Boolean) {
val notEqualLabel = asmgen.makeLabel("for_not_equal")
val compareDoneLabel = asmgen.makeLabel("for_compare_done")
if (signed) {
asmgen.out("""
lda $loopvar
cmp $toValueVar
bne $notEqualLabel
jmp $loopLabel
$notEqualLabel
sec
lda $loopvar
sbc $toValueVar
bvc $compareDoneLabel
eor #$80
$compareDoneLabel
${if (ascending) "bpl" else "bmi"} $endLabel
""")
} else if (ascending) {
asmgen.out("""
lda $toValueVar
cmp $loopvar
bcc $endLabel
jmp $loopLabel
""")
} else {
asmgen.out("""
lda $loopvar
cmp $toValueVar
bcc $endLabel
""")
}
}
private fun emitByteRangeUpdate(
loopvar: String,
toValueVar: String,
stepVar: String,
nextVar: String,
endLabel: String,
loopLabel: String,
loopvarSigned: Boolean,
stepSigned: Boolean
) {
val notLastLabel = asmgen.makeLabel("for_not_last")
val ascendingCompareDoneLabel = asmgen.makeLabel("for_next_compare_done")
val descendingCompareDoneLabel = if(stepSigned) asmgen.makeLabel("for_desc_compare_done") else null
val descendingUpdateLabel = if(stepSigned) asmgen.makeLabel("for_desc_update") else null
val storeNextLabel = asmgen.makeLabel("for_store_next")
val ascendingWrapBranch = if (loopvarSigned) "bvs" else "bcs"
val descendingWrapBranch = if (loopvarSigned) "bvs" else "bcc"
asmgen.out("""
lda $loopvar
cmp $toValueVar
bne $notLastLabel
jmp $endLabel
$notLastLabel
""")
if (stepSigned)
asmgen.out(" lda $stepVar | bmi $descendingUpdateLabel")
asmgen.out("""
clc
lda $loopvar
adc $stepVar
$ascendingWrapBranch $endLabel
sta $nextVar
""")
asmgen.out("""
lda $nextVar
cmp $toValueVar
beq $storeNextLabel
sec
lda $nextVar
sbc $toValueVar
bvc $ascendingCompareDoneLabel
eor #$80
$ascendingCompareDoneLabel
""")
if (loopvarSigned)
asmgen.out(" bpl $endLabel")
else
asmgen.out(" bcs $endLabel")
if(stepSigned) {
asmgen.out("""
jmp $storeNextLabel
$descendingUpdateLabel
lda $loopvar
cmp $toValueVar
beq $endLabel
clc
lda $loopvar
adc $stepVar
$descendingWrapBranch $endLabel
sta $nextVar
""")
if (loopvarSigned)
asmgen.out("""
sec
lda $nextVar
sbc $toValueVar
bvc $descendingCompareDoneLabel
eor #$80
$descendingCompareDoneLabel
bmi $endLabel
""")
else
asmgen.out("""
lda $nextVar
cmp $toValueVar
bcc $endLabel
""")
}
asmgen.out("""
jmp $storeNextLabel
$storeNextLabel
lda $nextVar
sta $loopvar
jmp $loopLabel
""")
}
private fun forOverNonconstStepWordRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val loopDt = iterableDt.elementType()
val loopvar = asmgen.asmVariableName(stmt.variable)
val toValueVar = asmgen.createTempVarReused(loopDt.base, false, range)
val stepVar = asmgen.createTempVarReused(range.step.type.base, false, range)
val nextVar = asmgen.createTempVarReused(loopDt.base, false, range)
val loopLabel = asmgen.makeLabel("for_loop")
val descendingLabel = if(range.step.type.isSigned) asmgen.makeLabel("for_desc") else null
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
// Keep range expression evaluation in source order and preserve each value across the next one.
asmgen.assignExpressionToVariable(range.from, loopvar, loopDt)
asmgen.assignExpressionToVariable(range.to, toValueVar, loopDt)
asmgen.assignExpressionToVariable(range.step, stepVar, range.step.type)
if (range.step.type.isSigned)
asmgen.out(" lda $stepVar | ora ${stepVar}+1 | beq $endLabel | lda ${stepVar}+1 | bmi $descendingLabel")
else
asmgen.out(" lda $stepVar | ora ${stepVar}+1 | beq $endLabel")
emitWordRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, true)
asmgen.out(loopLabel)
asmgen.translate(stmt.statements)
emitWordRangeUpdate(loopvar, toValueVar, stepVar, nextVar, endLabel, loopLabel, loopDt.isSigned, range.step.type.isSigned)
if(descendingLabel != null) {
asmgen.out(descendingLabel)
emitWordRangePrecheck(loopvar, toValueVar, loopLabel, endLabel, loopDt.isSigned, false)
asmgen.out(" jmp $loopLabel")
}
asmgen.out(endLabel)
}
private fun emitWordRangePrecheck(loopvar: String, toValueVar: String, loopLabel: String, endLabel: String, signed: Boolean, ascending: Boolean) {
val notEqualLabel = asmgen.makeLabel("for_not_equal")
val compareDoneLabel = asmgen.makeLabel("for_compare_done")
if (signed) {
asmgen.out("""
lda $loopvar+1
cmp $toValueVar+1
bne $notEqualLabel
lda $loopvar
cmp $toValueVar
bne $notEqualLabel
jmp $loopLabel
$notEqualLabel
sec
lda $loopvar
sbc $toValueVar
lda $loopvar+1
sbc $toValueVar+1
bvc $compareDoneLabel
eor #$80
$compareDoneLabel
${if (ascending) "bpl" else "bmi"} $endLabel
""")
} else if (ascending) {
asmgen.out("""
lda $toValueVar+1
cmp $loopvar+1
bcc $endLabel
bne $loopLabel
lda $toValueVar
cmp $loopvar
bcc $endLabel
jmp $loopLabel
""")
} else {
asmgen.out("""
lda $loopvar+1
cmp $toValueVar+1
bcc $endLabel
bne $loopLabel
lda $loopvar
cmp $toValueVar
bcc $endLabel
jmp $loopLabel
""")
}
}
private fun emitWordRangeUpdate(
loopvar: String,
toValueVar: String,
stepVar: String,
nextVar: String,
endLabel: String,
loopLabel: String,
loopvarSigned: Boolean,
stepSigned: Boolean
) {
val notLastLabel = asmgen.makeLabel("for_not_last")
val ascendingCompareLabel = asmgen.makeLabel("for_next_compare")
val ascendingCompareDoneLabel = asmgen.makeLabel("for_next_compare_done")
val descendingCompareDoneLabel = if(stepSigned) asmgen.makeLabel("for_desc_compare_done") else null
val descendingUpdateLabel = if(stepSigned) asmgen.makeLabel("for_desc_update") else null
val descendingNotLastLabel = if(stepSigned) asmgen.makeLabel("for_desc_not_last") else null
val storeNextLabel = asmgen.makeLabel("for_store_next")
val ascendingWrapBranch = if (loopvarSigned) "bvs" else "bcs"
val descendingWrapBranch = if (loopvarSigned) "bvs" else "bcc"
asmgen.out("""
lda $loopvar+1
cmp $toValueVar+1
bne $notLastLabel
lda $loopvar
cmp $toValueVar
beq $endLabel
$notLastLabel
""")
if (stepSigned)
asmgen.out(" lda ${stepVar}+1 | bmi $descendingUpdateLabel")
asmgen.out("""
clc
lda $loopvar
adc $stepVar
sta $nextVar
lda $loopvar+1
adc $stepVar+1
$ascendingWrapBranch $endLabel
sta $nextVar+1
""")
asmgen.out("""
lda $nextVar+1
cmp $toValueVar+1
bne $ascendingCompareLabel
lda $nextVar
cmp $toValueVar
beq $storeNextLabel
$ascendingCompareLabel
sec
lda $nextVar
sbc $toValueVar
lda $nextVar+1
sbc $toValueVar+1
bvc $ascendingCompareDoneLabel
eor #$80
$ascendingCompareDoneLabel
""")
if (loopvarSigned)
asmgen.out(" bpl $endLabel")
else
asmgen.out(" bcs $endLabel")
if(stepSigned) {
asmgen.out("""
jmp $storeNextLabel
$descendingUpdateLabel
lda $loopvar+1
cmp $toValueVar+1
bne $descendingNotLastLabel
lda $loopvar
cmp $toValueVar
beq $endLabel
$descendingNotLastLabel
clc
lda $loopvar
adc $stepVar
sta $nextVar
lda $loopvar+1
adc $stepVar+1
$descendingWrapBranch $endLabel
sta $nextVar+1
""")
if (loopvarSigned)
asmgen.out("""
sec
lda $nextVar
sbc $toValueVar
lda $nextVar+1
sbc $toValueVar+1
bvc $descendingCompareDoneLabel
eor #$80
$descendingCompareDoneLabel
bmi $endLabel
""")
else
asmgen.out("""
lda $nextVar+1
cmp $toValueVar+1
bcc $endLabel
bne $storeNextLabel
lda $nextVar
cmp $toValueVar
bcc $endLabel
""")
}
else asmgen.out(" jmp $storeNextLabel")
asmgen.out("""
$storeNextLabel
lda $nextVar
sta $loopvar
lda $nextVar+1
sta $loopvar+1
jmp $loopLabel
""")
}
private fun forOverNonconstLongRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val stepsize = range.step.asConstInteger()!!
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
if (stepsize == 1 || stepsize == -1)
forOverLongsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
else
forOverLongsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
}
private fun forOverLongsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
val stepsize = range.step.asConstInteger()!!
// Use temporary variable to store the limit
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToVariable(range.to, toValueVar, iterableDt.elementType())
if (stepsize == 1) {
// Increment toValueVar to make the loop inclusive (0 to N runs N+1 times)
asmgen.out("""
inc $toValueVar
bne +
inc $toValueVar+1
bne +
inc $toValueVar+2
bne +
inc $toValueVar+3
+
""")
} else {
// Decrement toValueVar to make the loop inclusive (N downto 0 runs N+1 times)
val label1 = asmgen.makeLabel("dec_1")
val label2 = asmgen.makeLabel("dec_2")
val label3 = asmgen.makeLabel("dec_3")
asmgen.out("""
lda $toValueVar
bne $label1
lda $toValueVar+1
bne $label2
lda $toValueVar+2
bne $label3
dec $toValueVar+3
$label3: dec $toValueVar+2
$label2: dec $toValueVar+1
$label1: dec $toValueVar
""")
}
asmgen.out(loopLabel)
asmgen.translate(forloop.statements)
if (stepsize == 1) {
asmgen.out("""
inc $varname
bne +
inc $varname+1
bne +
inc $varname+2
bne +
inc $varname+3
+ lda $varname
cmp $toValueVar
bne $loopLabel
lda $varname+1
cmp $toValueVar+1
bne $loopLabel
lda $varname+2
cmp $toValueVar+2
bne $loopLabel
lda $varname+3
cmp $toValueVar+3
bne $loopLabel
""")
} else {
asmgen.out("""
lda $varname
bne +++
lda $varname+1
bne ++
lda $varname+2
bne +
dec $varname+3
+ dec $varname+2
+ dec $varname+1
+ dec $varname
lda $varname
cmp $toValueVar
bne $loopLabel
lda $varname+1
cmp $toValueVar+1
bne $loopLabel
lda $varname+2
cmp $toValueVar+2
bne $loopLabel
lda $varname+3
cmp $toValueVar+3
bne $loopLabel
""")
}
asmgen.out(endLabel)
}
private fun forOverLongsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
TODO("legacy 6502 codegen does not support long FOR ranges with step other than 1 or -1 at ${forloop.position}")
}
private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val stepsize = range.step.asConstInteger()!!
val loopLabel = asmgen.makeLabel("for_loop")
@@ -705,7 +1187,7 @@ $loopLabel sty $indexVar
when {
iterableDt.isString -> iterateStrings()
iterableDt.isByteArray || iterableDt.isBoolArray -> iterateBytes()
iterableDt.isSplitWordArray -> iterateSplitWords()
iterableDt.isSplitWordArray(asmgen.options.compTarget) -> iterateSplitWords()
iterableDt.isWordArray -> iterateWords()
iterableDt.isLongArray -> iterateWords(true)
iterableDt.isFloatArray -> throw AssemblyError("for loop with floating point variables is not supported")
@@ -803,7 +1285,7 @@ $loopLabel""")
}
asmgen.out(endLabel)
}
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> {
iterableDt.isWordArray && !iterableDt.isSplitWordArray(asmgen.options.compTarget) -> {
// loop over word range via loopvar, step >= 2 or <= -2
when (range.step) {
1, -1 -> {
@@ -974,16 +1456,15 @@ $loopLabel""")
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #<${range.last}
inc $varname
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel
+ inc $varname
inc $varname+1
+ lda $varname
cmp #<${range.last+1}
bne $loopLabel
inc $varname+1""")
asmgen.jmp(loopLabel)
lda $varname+1
cmp #>${range.last+1}
bne $loopLabel""")
asmgen.out(endLabel)
asmgen.loopEndLabels.removeLast()
}
@@ -1004,24 +1485,21 @@ $loopLabel""")
if(range.last==0) {
asmgen.out("""
lda $varname
bne ++
lda $varname+1
beq $endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #<${range.last}
bne +
lda $varname+1
cmp #>${range.last}
beq $endLabel""")
}
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
+ dec $varname
lda $varname
cmp #<${range.last-1}
bne $loopLabel
lda $varname+1
cmp #>${range.last-1}
bne $loopLabel""")
asmgen.out(endLabel)
asmgen.loopEndLabels.removeLast()
}
@@ -1033,7 +1511,7 @@ $loopLabel""")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
val from = range.first.toLongHex()
val to = range.last.toLongHex()
val toPlus1 = (range.last + 1).toLongHex()
asmgen.out("""
lda #$${from.substring(6, 8)}
sta $varname
@@ -1046,26 +1524,25 @@ $loopLabel""")
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #$${to.substring(6, 8)}
inc $varname
bne +
lda $varname+1
cmp #$${to.substring(4, 6)}
bne +
lda $varname+2
cmp #$${to.substring(2, 4)}
bne +
lda $varname+3
cmp #$${to.take(2)}
beq $endLabel
+ inc $varname
bne $loopLabel
inc $varname+1
bne $loopLabel
bne +
inc $varname+2
bne +
inc $varname+3
+ lda $varname
cmp #$${toPlus1.substring(6, 8)}
bne $loopLabel
inc $varname+3""")
asmgen.jmp(loopLabel)
lda $varname+1
cmp #$${toPlus1.substring(4, 6)}
bne $loopLabel
lda $varname+2
cmp #$${toPlus1.substring(2, 4)}
bne $loopLabel
lda $varname+3
cmp #$${toPlus1.take(2)}
bne $loopLabel""")
asmgen.out(endLabel)
asmgen.loopEndLabels.removeLast()
}
@@ -1077,7 +1554,7 @@ $loopLabel""")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
val from = range.first.toLongHex()
val to = range.last.toLongHex()
val toMinus1 = (range.last - 1).toLongHex()
asmgen.out("""
lda #$${from.substring(6, 8)}
sta $varname
@@ -1099,20 +1576,6 @@ $loopLabel""")
bne ++
lda $varname+3
beq $endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #$${to.substring(6, 8)}
bne +
lda $varname+1
cmp #$${to.substring(4, 6)}
bne +
lda $varname+2
cmp #$${to.substring(2, 4)}
bne +
lda $varname+3
cmp #$${to.take(2)}
beq $endLabel""")
}
asmgen.out("""
+ lda $varname
@@ -1124,8 +1587,19 @@ $loopLabel""")
dec $varname+3
+ dec $varname+2
+ dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
+ dec $varname
lda $varname
cmp #$${toMinus1.substring(6, 8)}
bne $loopLabel
lda $varname+1
cmp #$${toMinus1.substring(4, 6)}
bne $loopLabel
lda $varname+2
cmp #$${toMinus1.substring(2, 4)}
bne $loopLabel
lda $varname+3
cmp #$${toMinus1.take(2)}
bne $loopLabel""")
asmgen.out(endLabel)
asmgen.loopEndLabels.removeLast()
}
@@ -9,6 +9,16 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
import prog8.codegen.cpu6502.assignment.TargetStorageKind
/*
* CRITICAL: `inline asmsub` subroutines MUST be inlined at the call site.
* They are NOT called with jsr - their assembly body is copied directly into the caller.
* This means: no .proc/.pend, no rts, no jsr. The return value is left in the appropriate
* CPU register (A/X/Y) and the caller picks it up.
*
* This is handled at line 68: `if (sub.inline) { ... }`.
* Any new codegen backend MUST replicate this behavior.
*/
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
@@ -33,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val symbol = asmgen.symbolTable.lookup(call.name)!!
val symbol = asmgen.symbolTable.lookup(call.name) ?: asmgen.symbolTable.lookupUnscoped(call.name) ?: throw IllegalStateException("Symbol not found: ${call.name}")
if(symbol.type == StNodeType.LABEL) {
require(call.void)
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
@@ -44,6 +54,26 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val subAsmName = asmgen.asmSymbolName(call.name)
if(sub is PtAsmSub) {
val varBank = sub.address?.varbank
var varbank: String? = null
var bankFixLabel: String? = null
if (varBank != null) {
// the bank variable can also be the name of a subroutine that should be called to return the bank byte dynamically
val bankSymbol = asmgen.symbolTable.lookup(varBank.name) ?: asmgen.symbolTable.lookupUnscoped(varBank.name)
if (bankSymbol?.type == StNodeType.SUBROUTINE || bankSymbol?.type == StNodeType.EXTSUB) {
val extsubId = asmgen.extsubCallSiteIds[sub]
if (extsubId != null) {
asmgen.out(" lda #${extsubId.toInt()}")
}
asmgen.out(" jsr ${asmgen.asmSymbolName(varBank.name)}")
// store bank byte directly into the JSRFAR instruction data (forward label reference)
bankFixLabel = asmgen.makeLabel("bank")
asmgen.out(" sta $bankFixLabel")
} else {
varbank = asmgen.asmVariableName(varBank)
}
}
argumentsViaRegisters(sub, call)
if (sub.inline) {
// inline the subroutine. (regardless of optimization settings!)
@@ -56,54 +86,57 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else {
val bank = sub.address?.constbank?.toString()
if(bank==null) {
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
if(varbank!=null) {
if(varbank!=null || bankFixLabel!=null) {
if(asmgen.options.romable)
TODO("no codegen yet for non-const bank in subroutine call that's usable in ROM ${call.position}")
// variable bank case: save/restore registers around the self-modifying sta
if(varbank!=null) {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp""")
}
val label = bankFixLabel ?: "+"
// self-modifying code: set jsrfar bank argument
when(asmgen.options.compTarget.name) {
"cx16" -> {
// JSRFAR can jump to a banked RAM address as well!
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr cx16.JSRFAR
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
$label .byte 0 ; modified"""
)
}
"c64" -> {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr c64.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
$label .byte 0 ; modified"""
)
}
"c128" -> {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr c128.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
$label .byte 0 ; modified"""
)
}
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
else -> {
if(asmgen.options.compTarget.supportsBankedCalls) {
val targetName = asmgen.options.compTarget.name
asmgen.out("""
jsr $targetName.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
$label .byte 0 ; modified"""
)
} else
throw AssemblyError("callfar is not supported on the selected compilation target")
}
}
} else {
asmgen.out(" jsr $subAsmName")
@@ -133,7 +166,17 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
.byte $bank"""
)
}
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
else -> {
if(asmgen.options.compTarget.supportsBankedCalls) {
val targetName = asmgen.options.compTarget.name
asmgen.out("""
jsr $targetName.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank"""
)
} else
throw AssemblyError("callfar is not supported on the selected compilation target")
}
}
}
}
@@ -234,7 +277,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
} else {
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
val argComplexity = call.args.map { usesOtherRegistersWhileEvaluating(it) }
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub, argComplexity)
optimalEvalOrder.forEach {
val param = sub.parameters[it]
val arg = call.args[it]
@@ -343,8 +387,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)
}
val src = if(value.type.isPassByRef) {
if(value is PtIdentifier) {
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, value.position)
if(value is PtIdentifier || value is PtPointerDeref) {
val addr = PtAddressOf(value.type.typeForUntypedAddressOf(false, program.memsizer), false, value.position)
addr.add(value)
addr.parent = scope as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
@@ -382,6 +426,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
if(argType.isUnsignedWord && paramType.isString)
return true
if (argType.isArray && (paramType.isString || paramType.isPointerToByte))
return true
return false
}
}
@@ -940,11 +940,10 @@ internal class IfElseAsmGen(private val program: PtProgram,
val baseVarname = asmgen.asmVariableName(e.variable!!)
val constIndex = e.index.asConstInteger()
if (constIndex != null) {
val idx = constIndex
if (e.splitWords) {
require(idx < 256)
require(constIndex < 256)
val suffix = if (long) "_3" else "_msb"
asmgen.out(" lda $baseVarname$suffix+$idx")
asmgen.out(" lda $baseVarname$suffix+$constIndex")
return
}
}
@@ -964,7 +963,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" cpy #0")
} else {
var varname = asmgen.asmVariableName(e.identifier!!)
if(e.identifier!!.type.isSplitWordArray) {
if(e.identifier!!.type.isSplitWordArray(asmgen.options.compTarget)) {
varname += if(e.isMsbForSplitArray) "_msb" else "_lsb"
}
asmgen.out(" lda #>$varname")
@@ -1580,7 +1579,13 @@ internal class IfElseAsmGen(private val program: PtProgram,
lda $opL+3
sbc $varR+3""")
} else {
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
}
asmgen.assignExpressionToRegister(r, RegisterOrPair.R14R15, true)
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
}
asmgen.out("""
sec
lda $opL
@@ -1828,9 +1833,9 @@ internal class IfElseAsmGen(private val program: PtProgram,
if (e is PtIdentifier) {
val varname = asmgen.asmVariableName(e)
block(varname, "$varname+1")
} else if (e is PtAddressOf && !e.isFromArrayElement) {
} else if (e is PtAddressOf && !e.isFromArrayElement && e.identifier != null) {
val identifier = e.identifier!!
val varname = if (identifier.type.isSplitWordArray) {
val varname = if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
if (e.isMsbForSplitArray) identifier.name + "_msb" else identifier.name + "_lsb"
} else {
identifier.name
@@ -83,7 +83,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(endLabel)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
expr.type.isWord || expr.type.isString -> {
expr.type.isWord || expr.type.isString || expr.type.isPointer -> {
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
asmgen.jmp(endLabel)
asmgen.out(trueLabel)
@@ -656,7 +656,13 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
lda $opL+3
sbc $varR+3""")
} else {
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
}
asmgen.assignExpressionToRegister(r, RegisterOrPair.R14R15, true)
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
}
asmgen.out("""
sec
lda $opL
@@ -717,7 +723,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
is PtAddressOf -> {
val identifier = e.identifier!!
var varname = asmgen.asmVariableName(identifier)
if (identifier.type.isSplitWordArray) {
if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
varname += if (e.isMsbForSplitArray) "_msb" else "_lsb"
}
asmgen.out(" lda #<$varname | ora #>$varname | bne $falseLabel")
@@ -768,7 +774,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
is PtAddressOf -> {
val identifier = e.identifier!!
var varname = asmgen.asmVariableName(identifier)
if (identifier.type.isSplitWordArray) {
if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
varname += if (e.isMsbForSplitArray) "_msb" else "_lsb"
}
asmgen.out(" lda #<$varname | ora #>$varname | beq $falseLabel")
@@ -1094,11 +1100,10 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
val baseVarname = asmgen.asmVariableName(e.variable!!)
val constIndex = e.index.asConstInteger()
if (constIndex != null) {
val idx = constIndex
if (e.splitWords) {
require(idx < 256)
require(constIndex < 256)
val suffix = if (long) "_3" else "_msb"
asmgen.out(" lda $baseVarname$suffix+$idx")
asmgen.out(" lda $baseVarname$suffix+$constIndex")
return
}
}
@@ -1118,7 +1123,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(" cpy #0")
} else {
var varname = asmgen.asmVariableName(e.identifier!!)
if(e.identifier!!.type.isSplitWordArray) {
if(e.identifier!!.type.isSplitWordArray(asmgen.options.compTarget)) {
varname += if(e.isMsbForSplitArray) "_msb" else "_lsb"
}
asmgen.out(" lda #>$varname")
@@ -1150,7 +1155,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
} else if (e is PtAddressOf && !e.isFromArrayElement) {
val identifier = e.identifier!!
var varname = asmgen.asmVariableName(identifier)
if (identifier.type.isSplitWordArray) {
if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
varname += if (e.isMsbForSplitArray) "_msb" else "_lsb"
}
block("#<$varname", "#>$varname")
@@ -173,6 +173,7 @@ internal class ProgramAndVarsGen(
asmgen.out(" jsr p8_sys_startup.init_system")
asmgen.out(" jsr p8_sys_startup.init_system_phase2")
}
else -> error("Unsupported output type: ${compTarget.defaultOutputType}")
}
if (options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
@@ -223,20 +224,23 @@ internal class ProgramAndVarsGen(
private fun footer() {
asmgen.out(" .dsection STRUCTINSTANCES\n")
var relocateBssVars = false
var relocateBssSlabs = false
var relocatedBssStart = 0u
var relocatedBssEnd = 0u
var relocateVars = false
var relocatedStart = 0u
var relocatedEnd = 0u
if(options.varsGolden) {
if(options.varsAddress != null) {
relocateVars = true
relocatedStart = options.varsAddress!!
relocatedEnd = if(options.memtopAddress != 0u) options.memtopAddress else 0xFFFFu
} else if(options.varsGolden) {
if(options.compTarget.BSSGOLDENRAM_START == 0u ||
options.compTarget.BSSGOLDENRAM_END == 0u ||
options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) {
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
}
relocateBssVars = true
relocatedBssStart = options.compTarget.BSSGOLDENRAM_START
relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END
relocateVars = true
relocatedStart = options.compTarget.BSSGOLDENRAM_START
relocatedEnd = options.compTarget.BSSGOLDENRAM_END
}
else if(options.varsHighBank!=null) {
if(options.compTarget.BSSHIGHRAM_START == 0u ||
@@ -244,67 +248,34 @@ internal class ProgramAndVarsGen(
options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
}
if(options.slabsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
throw AssemblyError("slabs and vars high bank must be the same")
relocateBssVars = true
relocatedBssStart = options.compTarget.BSSHIGHRAM_START
relocatedBssEnd = options.compTarget.BSSHIGHRAM_END
}
if(options.slabsGolden) {
if(options.compTarget.BSSGOLDENRAM_START == 0u ||
options.compTarget.BSSGOLDENRAM_END == 0u ||
options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) {
throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available")
}
relocateBssSlabs = true
relocatedBssStart = options.compTarget.BSSGOLDENRAM_START
relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END
}
else if(options.slabsHighBank!=null) {
if(options.compTarget.BSSHIGHRAM_START == 0u ||
options.compTarget.BSSHIGHRAM_END == 0u ||
options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) {
throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available")
}
if(options.varsHighBank!=null && options.varsHighBank!=options.slabsHighBank)
throw AssemblyError("slabs and vars high bank must be the same")
relocateBssSlabs = true
relocatedBssStart = options.compTarget.BSSHIGHRAM_START
relocatedBssEnd = options.compTarget.BSSHIGHRAM_END
relocateVars = true
relocatedStart = options.compTarget.BSSHIGHRAM_START
relocatedEnd = options.compTarget.BSSHIGHRAM_END
}
asmgen.out("; bss sections")
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
if(relocateBssVars) {
if(!relocateBssSlabs)
asmgen.out(" .dsection BSS_SLABS")
if(relocateVars) {
asmgen.out("prog8_program_end\t; end of program label for progend()")
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out(" * = ${relocatedStart.toHex()}")
asmgen.out(" .dsection BSS_NOCLEAR")
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
if(relocateBssSlabs)
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .cerror * > ${relocatedEnd.toHex()}, \"too many variables/data for BSS section\"")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
} else {
asmgen.out(" .dsection BSS_NOCLEAR")
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
if(!relocateBssSlabs)
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out("prog8_program_end\t; end of program label for progend()")
if(relocateBssSlabs) {
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for BSS_SLABS section\"")
}
}
if(relocatedBssEnd >= options.memtopAddress)
options.memtopAddress = relocatedBssEnd+1u
val effectiveEnd = if(relocateVars) relocatedEnd else 0u
if(effectiveEnd >= options.memtopAddress && effectiveEnd != 0u)
options.memtopAddress = effectiveEnd+1u
asmgen.out(" ; memtop check")
asmgen.out(" .cerror * >= ${options.memtopAddress.toHex()}, \"Program too long by \", * - ${(options.memtopAddress-1u).toHex()}, \" bytes, memtop=${options.memtopAddress.toHex()}\"")
@@ -372,13 +343,14 @@ internal class ProgramAndVarsGen(
}
private fun asmTypeString(dt: DataType): String {
val effectiveDt = if(dt.isArray) dt.elementType() else dt
return when {
dt.isBool || dt.isUnsignedByte -> ".byte"
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isLong -> ".dint"
dt.isFloat -> ".byte"
effectiveDt.isBool || effectiveDt.isUnsignedByte -> ".byte"
effectiveDt.isSignedByte -> ".char"
effectiveDt.isUnsignedWord || effectiveDt.isPointer -> ".word"
effectiveDt.isSignedWord -> ".sint"
effectiveDt.isLong -> ".dint"
effectiveDt.isFloat -> ".byte"
else -> {
throw AssemblyError("weird dt")
}
@@ -387,42 +359,56 @@ internal class ProgramAndVarsGen(
private fun structInstances2asm() {
fun initValues(instance: StStructInstance): List<String> {
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
if(field.first.isFloat) {
val numValue = (value as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for float field")
"["+compTarget.getFloatAsmBytes(numValue)+"]"
} else {
when (value) {
is StArrayElement.Number -> {
if(field.first.isPointer)
"$"+value.value.toInt().toString(16)
else if(field.first.isInteger)
value.value.toInt().toString()
else
value.value.toString()
}
is StArrayElement.AddressOf -> value.symbol
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
}
fun formatValue(value: StArrayElement, dt: DataType): String {
if(dt.isFloat) {
val numValue = (value as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for float field")
return "["+compTarget.getFloatAsmBytes(numValue)+"]"
}
return when (value) {
is StArrayElement.Number -> {
if(dt.isPointer)
"$"+value.value.toInt().toString(16)
else if(dt.isInteger)
value.value.toInt().toString()
else
value.value.toString()
}
is StArrayElement.AddressOf -> value.symbol
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
}
}
fun flatInitValues(instance: StStructInstance, structtype: StStruct): List<String> {
var valueIndex = 0
return structtype.fields.flatMap { field ->
val arraySz = field.arraySize
val count = arraySz ?: 1
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
(0 until count).map {
val value = instance.initialValues[valueIndex++]
formatValue(value, elemDt)
}
}
}
asmgen.out("; struct types")
symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
val paramFields = structtype.fields.filter { !it.isArray }
val structargs = paramFields.indices.joinToString(",") { "f$it" }
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
structtype.fields.withIndex().forEach { (index, field) ->
val dt = field.first
val varname = "f${index}"
val type = asmTypeString(dt)
asmgen.out("p8v_${field.second} $type \\$varname") // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST
var paramIdx = 0
for(field in structtype.fields) {
val type = asmTypeString(field.type)
if(field.isArray) {
val anonymous = List(field.arraySize!!) { "?" }.joinToString(",")
asmgen.out("p8v_${field.name} $type $anonymous")
} else {
asmgen.out("p8v_${field.name} $type \\f$paramIdx")
paramIdx++
}
}
asmgen.out(" .endstruct\n")
}
@@ -433,14 +419,7 @@ internal class ProgramAndVarsGen(
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
instancesNoInit.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val zerovalues = structtype.fields.map { field ->
if(field.first.isFloat) {
val floatbytes = List(compTarget.memorySize(BaseDataType.FLOAT)) { "?" }
"[${floatbytes.joinToString(",")}]"
}
else "?"
}
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
asmgen.out("${it.name} .fill ${structtype.size} ; struct ${it.structName}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send BSS\n")
@@ -448,9 +427,28 @@ internal class ProgramAndVarsGen(
asmgen.out("; struct instances with initialization values\n")
asmgen.out(" .section STRUCTINSTANCES\n")
asmgen.out("$StStructInstanceBlockName .block\n")
instances.forEach {
val instancename = it.name.substringAfter('.')
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
instances.forEach { instance ->
val instancename = instance.name.substringAfter('.')
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
val values = flatInitValues(instance, structtype)
val hasArrays = structtype.fields.any { fld -> fld.isArray }
if(hasArrays) {
asmgen.out(instancename)
var valueIndex = 0
for(field in structtype.fields) {
val arraySz = field.arraySize
val count = arraySz ?: 1
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
val type = asmTypeString(elemDt)
val fieldValues = (0 until count).joinToString(",") {
formatValue(instance.initialValues[valueIndex++], elemDt)
}
asmgen.out(" $type $fieldValues")
}
asmgen.out("\n")
} else {
asmgen.out("$instancename .dstruct ${instance.structName}, ${values.joinToString(",")}\n")
}
}
asmgen.out(" .endblock\n")
asmgen.out(" .send STRUCTINSTANCES\n")
@@ -617,18 +615,26 @@ internal class ProgramAndVarsGen(
arrayVarsWithInitInZp.forEach {
val size = it.alloc.size
val name = asmgen.asmVariableName(it.name)
asmgen.out("""
lda #<${name}_init_value
ldy #>${name}_init_value
sta cx16.r0
sty cx16.r0+1
lda #<${name}
ldy #>${name}
sta cx16.r1
sty cx16.r1+1
lda #<$size
ldy #>$size
jsr sys.memcopy""")
if(it.alloc.dt.isSplitWordArray(compTarget)) {
val halfSize = size / 2
if(halfSize>0) {
asmgen.out("""
ldx #${halfSize-1}
- lda ${name}_init_value_lsb,x
sta ${name}_lsb,x
lda ${name}_init_value_msb,x
sta ${name}_msb,x
dex
bpl -""")
}
} else {
asmgen.out("""
ldx #${size-1}
- lda ${name}_init_value,x
sta ${name},x
dex
bpl -""")
}
}
asmgen.out(" jmp +")
}
@@ -688,7 +694,7 @@ internal class ProgramAndVarsGen(
if (scopedName.startsWith("cx16.r"))
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
val variable = symboltable.flat.getValue(scopedName) as StStaticVariable
if(variable.dt.isSplitWordArray) {
if(variable.dt.isSplitWordArray(compTarget)) {
val lsbAddr = zpvar.address
val msbAddr = zpvar.address + (zpvar.size/2).toUInt()
asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)")
@@ -780,16 +786,17 @@ internal class ProgramAndVarsGen(
dt.isSignedWord -> asmgen.out("${variable.name}\t.sint ?")
dt.isLong -> asmgen.out("${variable.name}\t.dint ?")
dt.isFloat -> asmgen.out("${variable.name}\t.fill ${compTarget.FLOAT_MEM_SIZE}")
dt.isSplitWordArray -> {
dt.isSplitWordArray(compTarget) -> {
alignVar(variable.align)
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!.toInt()) / 2
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf ; split word array of ${variable.length} ${variable.dt.elementType()} (lsbs)")
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf ; split word array of ${variable.length} ${variable.dt.elementType()} (msbs)")
}
dt.isArray -> {
alignVar(variable.align)
val numbytes = compTarget.memorySize(variable.dt, variable.length!!.toInt())
asmgen.out("${variable.name}\t.fill $numbytes")
val amount = variable.length!!.toInt()
val elementSize = compTarget.memorySize(variable.dt.elementType(), null)
asmgen.out("${variable.name}\t.fill $amount * $elementSize ; array of $amount ${variable.dt.elementType()} ")
}
dt.isPointer -> asmgen.out("${variable.name}\t.word ?") // a pointer is just an uword address
dt.isPointerArray -> {
@@ -871,17 +878,17 @@ internal class ProgramAndVarsGen(
asmgen.out(" .char " + chunk.joinToString())
}
}
dt.isSplitWordArray -> {
dt.isSplitWordArray(compTarget) -> {
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
asmgen.out("_array_$varname := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
asmgen.out("${varname}_msb\t.byte >_array_$varname")
asmgen.out("${varname}_words := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
asmgen.out("${varname}_msb\t.byte >${varname}_words")
} else {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
asmgen.out("_array_$varname := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
asmgen.out("${varname}_msb\t.byte >_array_$varname")
asmgen.out("${varname}_words := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
asmgen.out("${varname}_msb\t.byte >${varname}_words")
}
}
dt.isUnsignedWordArray -> {
@@ -925,6 +932,50 @@ internal class ProgramAndVarsGen(
for (f in array.zip(floatFills))
asmgen.out(" .byte ${f.second} ; float ${f.first}")
}
dt.base==BaseDataType.ARRAY && dt.sub==BaseDataType.STRUCT_INSTANCE -> {
val struct = dt.subType as? StStruct ?: throw AssemblyError("struct type not found for $varname")
val structSize = struct.size.toInt()
if(value==null) {
val totalBytes = (orNumberOfZeros ?: 0) * structSize
if(totalBytes==0) {
asmgen.out("$varname: .byte 0")
} else {
asmgen.out("$varname: .fill $totalBytes, 0")
}
} else {
// initialized struct array: value contains StructInstance elements
asmgen.out("$varname ; array of ${value.size} ${struct.name} structs")
// expand struct field types for per-element handling
val expandedFieldTypes = struct.fields.flatMap { field ->
val arraySz = field.arraySize
if(arraySz!=null) List(arraySz) { field.type } else listOf(field.type)
}
for(elt in value) {
val instanceName = (elt as? StArrayElement.StructInstance)?.name ?: throw AssemblyError("expected struct instance in array init for $varname")
val instance = symboltable.lookup(instanceName) as? StStructInstance ?: throw AssemblyError("struct instance $instanceName not found")
val fieldValues = instance.initialValues
require(fieldValues.size==expandedFieldTypes.size) { "field count mismatch for $instanceName" }
for((fieldType, fieldVal) in expandedFieldTypes.zip(fieldValues)) {
val num = when(fieldVal) {
is StArrayElement.Number -> fieldVal.value
is StArrayElement.BoolValue -> if(fieldVal.value) 1.0 else 0.0
else -> throw AssemblyError("struct field value must be number for $varname (got $fieldVal)")
}
when {
fieldType.isByteOrBool -> asmgen.out(" .byte ${num.toInt()}")
fieldType.isWord -> asmgen.out(" .word ${num.toInt()}")
fieldType.isLong -> asmgen.out(" .dword ${num.toInt()}")
fieldType.isFloat -> {
val bytes = compTarget.getFloatAsmBytes(num)
asmgen.out(" .byte $bytes ; float $num")
}
fieldType.isPointer -> asmgen.out(" .word ${num.toInt()} ; pointer")
else -> throw AssemblyError("unsupported struct field type $fieldType")
}
}
}
}
}
else -> throw AssemblyError("require array dt")
}
}
@@ -996,7 +1047,7 @@ internal class ProgramAndVarsGen(
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
is StArrayElement.AddressOf -> {
val symbol = symboltable.lookup(it.symbol)!!
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray(compTarget))
asmgen.asmSymbolName(it.symbol+"_lsb")
else
asmgen.asmSymbolName(it.symbol)
@@ -1037,7 +1088,7 @@ internal class ProgramAndVarsGen(
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
is StArrayElement.AddressOf -> {
val symbol = symboltable.lookup(it.symbol)!!
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray(compTarget))
asmgen.asmSymbolName(it.symbol+"_lsb")
else
asmgen.asmSymbolName(it.symbol)
@@ -3,17 +3,21 @@ package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import com.github.michaelbull.result.onOk
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.*
internal class VariableAllocator(private val symboltable: SymbolTable,
private val options: CompilationOptions,
private val errors: IErrorReporter
private val errors: IErrorReporter,
private val program: PtProgram
) {
private val zeropage = options.compTarget.zeropage
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
private val allStaticVariables by lazy { collectAllVariables(symboltable) }
private val usageScores by lazy { computeUsageScores(allStaticVariables) }
init {
allocateZeropageVariables()
@@ -46,7 +50,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(options.zeropage== ZeropageType.DONTUSE)
return
val allVariables = collectAllVariables(symboltable)
val allVariables = allStaticVariables
val numberOfAllocatableVariables = allVariables.size
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
@@ -90,9 +94,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
}
// try to allocate the "don't care" interger variables into the zeropage until it is full.
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...? Simply sorting by size (bytes prioritized) has BAD results
// Prioritize by usage frequency: sort by computed hotness score (access frequency × type weight × loop depth)
if(errors.noErrors()) {
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { usageScores[it.scopedNameString] ?: 0 }
for (variable in sortedList) {
if(variable.dt.isIntegerOrBool || variable.dt.isPointer) {
if(zeropage.free.isEmpty()) {
@@ -129,4 +133,33 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
collect(st)
return vars.sortedBy { it.dt.base }
}
private fun computeUsageScores(vars: Collection<StStaticVariable>): Map<String, Int> {
val scores = mutableMapOf<String, Int>()
val varInfo = vars.associateBy { it.scopedNameString }
var loopDepth = 0
fun walk(node: PtNode) {
val isLoopEnter = node is PtForLoop || node is PtRepeatLoop
if(isLoopEnter) loopDepth++
if(node is PtIdentifier) {
val v = varInfo[node.name]
if(v != null) {
val weight = when {
v.dt.isPointer || v.dt.isWord -> 2
else -> 1
}
scores[node.name] = (scores[node.name] ?: 0) + weight * (loopDepth + 1)
}
}
node.children.forEach { walk(it) }
if(isLoopEnter) loopDepth--
}
walk(program)
return scores
}
}
@@ -125,10 +125,13 @@ internal class AnyExprAsmGen(
}
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
// C64/PET32 ROM T-variant entries skip CONUPK and need arisgn+Z flag set up beforehand.
// CX16 ROM entries are already fixed and handle this internally.
val tSuffix = if(asmgen.options.compTarget.name in listOf(C64Target.NAME, PETTarget.NAME)) "_NZ" else ""
when(expr.operator) {
"+" -> {
assignFloatOperandsToFACandARG(expr.left, expr.right)
asmgen.out(" jsr floats.FADDT")
asmgen.out(" jsr floats.FADDT$tSuffix")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
@@ -140,13 +143,13 @@ internal class AnyExprAsmGen(
}
"*" -> {
assignFloatOperandsToFACandARG(expr.left, expr.right)
asmgen.out(" jsr floats.FMULTT")
asmgen.out(" jsr floats.FMULTT$tSuffix")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
"/" -> {
assignFloatOperandsToFACandARG(expr.right, expr.left)
asmgen.out(" jsr floats.FDIVT")
asmgen.out(" jsr floats.FDIVT$tSuffix")
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
return true
}
@@ -3,7 +3,6 @@ package prog8.codegen.cpu6502.assignment
import prog8.code.StConstant
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.core.BaseDataType
import prog8.codegen.cpu6502.AsmGen6502Internal
@@ -237,7 +236,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
if(sub is PtSub && sub.signature.returns.size>1)
DataType.UNDEFINED // TODO list of types instead?
else
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
sub.returnsWhatWhere(asmgen.options.compTarget).firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
File diff suppressed because it is too large. Load diff
@@ -62,6 +62,63 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun inplaceModificationIndexedPointerField(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
val array = target.array!!
val elementDt = target.datatype
if(!elementDt.isByteOrBool && !elementDt.isWord)
throw AssemblyError("inplace modification indexed pointer field for type $elementDt not supported")
val indexedTarget = IndexedPtrTarget(target)
val ptr = ptrgen.indexedEffectiveAddress(indexedTarget)
val elementTemp = asmgen.createTempVarReused(elementDt.base, false, array)
if(elementDt.isByteOrBool) {
asmgen.out("""
ldy #0
lda ($ptr),y
sta $elementTemp""")
} else {
asmgen.out("""
ldy #0
lda ($ptr),y
sta $elementTemp
iny
lda ($ptr),y
sta $elementTemp+1""")
}
// save the computed address on the hardware stack so later code can't clobber it
asmgen.out("""
lda $ptr
pha
lda $ptr+1
pha""")
// reuse the existing variable-target logic to modify the temp
val tempTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, elementDt, null, target.position, variableAsmName = elementTemp)
inplaceModification(tempTarget, operator, value)
// restore the address and store the temp back
asmgen.out("""
pla
sta $ptr+1
pla
sta $ptr""")
if(elementDt.isByteOrBool) {
asmgen.out("""
ldy #0
lda $elementTemp
sta ($ptr),y""")
} else {
asmgen.out("""
ldy #0
lda $elementTemp
sta ($ptr),y
iny
lda $elementTemp+1
sta ($ptr),y""")
}
}
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: AsmAssignSource) {
// the asm-gen code can deal with situations where you want to assign a byte into a word.
@@ -247,7 +304,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
TargetStorageKind.ARRAY -> {
val deref = target.array!!.pointerderef
if(deref!=null) {
TODO("inplace modification array indexed pointer deref ${target.position}")
inplaceModificationIndexedPointerField(target, operator, value)
return
}
val targetArrayVar = target.array.variable!!
@@ -318,14 +375,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
SourceStorageKind.MEMORY -> TODO("inplace long modification with memread value ${target.position}")
SourceStorageKind.ARRAY -> TODO("inplace long modification with array value ${target.position}")
SourceStorageKind.EXPRESSION -> {
SourceStorageKind.EXPRESSION ->
if(value.expression is PtTypeCast) {
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
TODO("inplace long modification ${target.position}")
} else {
TODO("inplace long modification ${target.position}")
}
}
}
}
@@ -562,6 +618,28 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
internal fun inplacemodificationLongWithExpression(targetVar: String, operator: String, value: PtExpression) {
// Adding an unsigned byte to a long: skip zero-extending the byte into a
// full 4-byte long and just add it with carry propagation through the upper bytes.
// The byte shows up here as a (widened) long-typed cast of an ubyte expression,
// so detect it via the cast child type.
if(operator=="+" && value is PtTypeCast && value.value.type==DataType.UBYTE) {
assignmentAsmGen.assignExpressionToRegister(value.value, RegisterOrPair.A, false)
asmgen.out("""
clc
adc $targetVar+0
sta $targetVar+0
lda $targetVar+1
adc #0
sta $targetVar+1
lda $targetVar+2
adc #0
sta $targetVar+2
lda $targetVar+3
adc #0
sta $targetVar+3
""")
return
}
// it's not an expression so no need to preserve R14-R15
if(value.type.isSigned) {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
@@ -577,24 +655,40 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
when(operator) {
"+" -> {
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
asmgen.out("""
clc
ldx #252
- lda $targetVar+4,x
adc $sourceVar+4,x
sta $targetVar+4,x
inx
bne -""")
lda $targetVar+0
adc $sourceVar+0
sta $targetVar+0
lda $targetVar+1
adc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
adc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
adc $sourceVar+3
sta $targetVar+3
""")
}
"-" -> {
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
asmgen.out("""
sec
ldx #252
- lda $targetVar+4,x
sbc $sourceVar+4,x
sta $targetVar+4,x
inx
bne -""")
lda $targetVar+0
sbc $sourceVar+0
sta $targetVar+0
lda $targetVar+1
sbc $sourceVar+1
sta $targetVar+1
lda $targetVar+2
sbc $sourceVar+2
sta $targetVar+2
lda $targetVar+3
sbc $sourceVar+3
sta $targetVar+3
""")
}
"<<" -> {
asmgen.out("""
@@ -667,6 +761,53 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
dey
bpl -""")
}
"/" -> {
asmgen.out("""
; Copy target to R12R13 (dividend)
ldy #3
- lda $targetVar,y
sta cx16.r12,y
dey
bpl -
; Copy source to R14R15 (divisor)
ldy #3
- lda $sourceVar,y
sta cx16.r14,y
dey
bpl -
jsr prog8_math.div_longs
; Copy result from R14R15 back to target
ldy #3
- lda cx16.r14,y
sta $targetVar,y
dey
bpl -""")
}
"%" -> {
asmgen.out("""
; Copy target to R12R13 (dividend)
ldy #3
- lda $targetVar,y
sta cx16.r12,y
dey
bpl -
; Copy source to R14R15 (divisor)
ldy #3
- lda $sourceVar,y
sta cx16.r14,y
dey
bpl -
jsr prog8_math.div_longs
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to target
lda P8ZP_SCRATCH_W1
sta $targetVar
lda P8ZP_SCRATCH_W1+1
sta $targetVar+1
lda P8ZP_SCRATCH_W2
sta $targetVar+2
lda P8ZP_SCRATCH_W2+1
sta $targetVar+3""")
}
else -> {
TODO("in-place modify LONG with variable, operator=$operator")
}
@@ -869,8 +1010,12 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
else -> {
asmgen.out("""
lda #0
sta $variable
lda $variable+3
bpl +
lda #255
bne ++
+ lda #0
+ sta $variable
sta $variable+1
sta $variable+2
sta $variable+3""")
@@ -1021,6 +1166,65 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
dey
bpl -""")
}
"/" -> {
// long /= constant - use division routine
val lo = value and 0xFFFF
val hi = (value ushr 16) and 0xFFFF
asmgen.out("""
; Copy variable to R12R13 (dividend)
ldy #3
- lda $variable,y
sta cx16.r12,y
dey
bpl -
; Load constant into R14R15 (divisor)
lda #<${lo}
sta cx16.r14
lda #>${lo}
sta cx16.r14+1
lda #<${hi}
sta cx16.r15
lda #>${hi}
sta cx16.r15+1
jsr prog8_math.div_longs
; Copy result from R14R15 back to variable
ldy #3
- lda cx16.r14,y
sta $variable,y
dey
bpl -""")
}
"%" -> {
// long %= constant - use division routine, remainder is in P8ZP_SCRATCH_W1/W2
val lo = value and 0xFFFF
val hi = (value ushr 16) and 0xFFFF
asmgen.out("""
; Copy variable to R12R13 (dividend)
ldy #3
- lda $variable,y
sta cx16.r12,y
dey
bpl -
; Load constant into R14R15 (divisor)
lda #<${lo}
sta cx16.r14
lda #>${lo}
sta cx16.r14+1
lda #<${hi}
sta cx16.r15
lda #>${hi}
sta cx16.r15+1
jsr prog8_math.div_longs
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to variable
lda P8ZP_SCRATCH_W1
sta $variable
lda P8ZP_SCRATCH_W1+1
sta $variable+1
lda P8ZP_SCRATCH_W2
sta $variable+2
lda P8ZP_SCRATCH_W2+1
sta $variable+3""")
}
"<<" -> if (value > 0) inplaceLongShiftLeft()
">>" -> if (value > 0) inplaceLongShiftRight()
"|" -> {
@@ -1549,7 +1753,7 @@ $shortcutLabel:""")
// use the already existing optimized codegen for regular assignments x += array[index]
val binexpr = PtBinaryExpression(operator, dt, value.position)
binexpr.add(PtIdentifier(name, dt, value.position))
val arrayValue = PtArrayIndexer(value.type, value.position)
val arrayValue = PtArrayIndexer(value.type, valueVar.type.isSplitWordArray(program.memsizer), value.position)
arrayValue.add(valueVar)
arrayValue.add(value.index)
binexpr.add(arrayValue)
@@ -3149,6 +3353,7 @@ $shortcutLabel:""")
lda $name+1
sta P8ZP_SCRATCH_W1+1
txa
oweroff
jsr prog8_math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
File diff suppressed because it is too large. Load diff
@@ -113,34 +113,128 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
internal fun inplaceByteInvert(target: PtrTarget) {
TODO("inplace byte invert pointer deref ${target.position}")
val (zpPtrVar, offset) = deref(target.pointer)
val eorVal = if(target.dt.isBool) 1 else 255
if(offset==0.toUByte() && asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out(" lda ($zpPtrVar) | eor #$eorVal | sta ($zpPtrVar)")
} else {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
eor #$eorVal
sta ($zpPtrVar),y""")
}
}
internal fun inplaceWordInvert(target: PtrTarget) {
TODO("inplace word invert pointer deref ${target.position}")
val (zpPtrVar, offset) = deref(target.pointer)
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
eor #255
sta ($zpPtrVar),y
iny
lda ($zpPtrVar),y
eor #255
sta ($zpPtrVar),y""")
}
internal fun inplaceLongInvert(target: PtrTarget) {
val (zpPtrVar, offset) = deref(target.pointer)
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
eor #255
sta ($zpPtrVar),y
iny
lda ($zpPtrVar),y
eor #255
sta ($zpPtrVar),y
iny
lda ($zpPtrVar),y
eor #255
sta ($zpPtrVar),y
iny
lda ($zpPtrVar),y
eor #255
sta ($zpPtrVar),y""")
}
internal fun inplaceByteNegate(target: PtrTarget, scope: IPtSubroutine?) {
TODO("inplace byte negate to pointer deref ${target.position}")
val (zpPtrVar, offset) = deref(target.pointer)
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
eor #255
ina
sta ($zpPtrVar),y""")
} else {
asmgen.out("""
ldy #$offset
lda ($zpPtrVar),y
eor #255
clc
adc #1
sta ($zpPtrVar),y""")
}
}
internal fun inplaceWordNegate(target: PtrTarget, scope: IPtSubroutine?) {
TODO("inplace word negate pointer deref ${target.position}")
val (zpPtrVar, offset) = deref(target.pointer)
asmgen.out("""
ldy #$offset
sec
lda #0
sbc ($zpPtrVar),y
sta ($zpPtrVar),y
iny
lda #0
sbc ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
internal fun inplaceLongNegate(target: PtrTarget, scope: IPtSubroutine?) {
TODO("inplace long negate pointer deref ${target.position}")
val (zpPtrVar, offset) = deref(target.pointer)
asmgen.out("""
ldy #$offset
sec
lda #0
sbc ($zpPtrVar),y
sta ($zpPtrVar),y
iny
lda #0
sbc ($zpPtrVar),y
sta ($zpPtrVar),y
iny
lda #0
sbc ($zpPtrVar),y
sta ($zpPtrVar),y
iny
lda #0
sbc ($zpPtrVar),y
sta ($zpPtrVar),y""")
}
internal fun inplaceFloatNegate(target: PtrTarget, scope: IPtSubroutine?) {
TODO("inplace float negate pointer deref ${target.position}")
val (zpPtrVar, offset) = deref(target.pointer)
// flip sign bit in second byte of 5-byte float
asmgen.out(
$$"""
ldy #$$offset
iny
lda ($$zpPtrVar),y
eor #$80
sta ($$zpPtrVar),y"""
)
}
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false): Pair<String, UByte> {
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false, loadPointerFieldValue: Boolean=false): Pair<String, UByte> {
// walk the pointer deref chain and leaves the final pointer value in a ZP var
// this will often be the temp var P8ZP_SCRATCH_PTR but can also be the original pointer variable if it is already in zeropage and there is nothing to add to it
// returns the ZP var to use as a pointer, and a Y register offset (which can be zero)
// loadPointerFieldValue: for addOffsetToPointer=true, also load the value if the final field is a pointer-typed field (rather than returning its address).
fun addFieldOffsetToScratchPointer(fieldoffset: UInt) {
if(fieldoffset==0u)
@@ -185,7 +279,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
if(addOffsetToPointer) {
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
addFieldOffsetToScratchPointer(field.second.toUInt())
if(pointer.derefLast)
if(pointer.derefLast || (loadPointerFieldValue && field.first.isPointer))
updateScratchPointer()
return "P8ZP_SCRATCH_PTR" to 0u
} else {
@@ -235,12 +329,59 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
} else {
if(addOffsetToPointer) {
addFieldOffsetToScratchPointer(fieldinfo.second.toUInt())
if(loadPointerFieldValue && fieldinfo.first.isPointer)
updateScratchPointer()
return "P8ZP_SCRATCH_PTR" to 0u
} else
return "P8ZP_SCRATCH_PTR" to fieldinfo.second
}
}
internal fun indexedEffectiveAddress(target: IndexedPtrTarget): String {
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
// generic: evaluate index as word, scale by element size, add field-corrected base pointer
asmgen.assignExpressionToVariable(target.index, "P8ZP_SCRATCH_W1", DataType.UWORD)
when (eltSize) {
1 -> {}
2 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
4 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
8 -> asmgen.out(" asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1 | asl P8ZP_SCRATCH_W1 | rol P8ZP_SCRATCH_W1+1")
else -> {
// generic multiply by eltSize
asmgen.out("""
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
sta prog8_math.multiply_words.multiplier
sty prog8_math.multiply_words.multiplier+1
lda #<$eltSize
ldy #>$eltSize
jsr prog8_math.multiply_words
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1""")
}
}
// save scaled index to W2, compute base (field-corrected) into P8ZP_SCRATCH_PTR, then add
asmgen.out(" lda P8ZP_SCRATCH_W1 | sta P8ZP_SCRATCH_W2 | lda P8ZP_SCRATCH_W1+1 | sta P8ZP_SCRATCH_W2+1")
val (basePtr, _) = deref(target.pointer, addOffsetToPointer = true, loadPointerFieldValue = true)
if(basePtr != "P8ZP_SCRATCH_PTR") {
// deref returned the original zp pointer variable; copy it so we don't mutate it
asmgen.out("""
lda $basePtr
sta P8ZP_SCRATCH_PTR
lda $basePtr+1
sta P8ZP_SCRATCH_PTR+1""")
}
asmgen.out("""
clc
lda P8ZP_SCRATCH_PTR
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_PTR
lda P8ZP_SCRATCH_PTR+1
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_PTR+1""")
return "P8ZP_SCRATCH_PTR"
}
internal fun assignPointerDerefExpression(target: AsmAssignTarget, value: PtPointerDeref) {
val (zpPtrVar, offset1) = deref(value)
val offset: UByte
@@ -309,24 +450,24 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
internal fun inplaceModification(target: PtrTarget, operator: String, value: AsmAssignSource) {
when (operator) {
"+" -> {
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
if(target.dt.isByte && (value.number?.number==1.0 || value.number?.number==2.0)) {
val amount = value.number.number.toInt()
inplaceByteInc(target, amount)
} else {
if (target.dt.isByte) inplaceByteAdd(target, value)
else if (target.dt.isWord) inplaceWordAdd(target, value)
else if (target.dt.isWord || target.dt.isPointer) inplaceWordAdd(target, value)
else if (target.dt.isFloat) inplaceFloatAddOrMul(target, "FADD", value)
else if (target.dt.isLong) inplaceLongAdd(target, value)
else throw AssemblyError("weird dt ${target.dt} ${target.position}")
}
}
"-" -> {
if(target.dt.isByte && value.number?.number==1.0 || value.number?.number==2.0) {
if(target.dt.isByte && (value.number?.number==1.0 || value.number?.number==2.0)) {
val amount = value.number.number.toInt()
inplaceByteDec(target, amount)
} else {
if (target.dt.isByte) inplaceByteSub(target, value)
else if (target.dt.isWord) inplaceWordSub(target, value)
else if (target.dt.isWord || target.dt.isPointer) inplaceWordSub(target, value)
else if (target.dt.isFloat) inplaceFloatSubOrDiv(target, "FSUB", value)
else if (target.dt.isLong) inplaceLongSub(target, value)
else throw AssemblyError("weird dt ${target.position}")
@@ -409,132 +550,163 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
internal fun assignByte(target: IndexedPtrTarget, byte: Int) {
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
val constIndex = target.index.asConstInteger()
if(constIndex!=null) {
val (zpPtrVar, offset2) = deref(target.pointer)
val offset = eltSize*constIndex + offset2.toInt()
if(offset>255) {
// need to add index to the pointer (but not clobber it) to allow index>255
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) { // TODO not very robust....
asmgen.out("""
lda $zpPtrVar+1
clc
adc #>$offset
sta $zpPtrVar+1
ldy #<$offset
lda #$byte
sta ($zpPtrVar),y""")
} else {
asmgen.out("""
lda $zpPtrVar
sta P8ZP_SCRATCH_PTR
lda $zpPtrVar+1
clc
adc #>$offset
sta P8ZP_SCRATCH_PTR+1
ldy #<$offset
lda #$byte
sta (P8ZP_SCRATCH_PTR),y""")
}
} else {
asmgen.out("""
ldy #<$offset
lda #$byte
sta ($zpPtrVar),y""")
}
} else if(target.index is PtIdentifier) {
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
val indexVarName = asmgen.asmVariableName(target.index)
if(eltSize!=1) {
TODO("multiply index by element size $eltSize ${target.position}")
// asmgen.loadScaledArrayIndexIntoRegister() ...
}
// element size is 1, can immediately add the index value
if(target.index.type.isWord) {
asmgen.out("""
clc
lda $zpPtrVar+1
adc $indexVarName+1
sta $zpPtrVar+1""")
}
asmgen.out("""
ldy $indexVarName
lda #$byte
sta ($zpPtrVar),y""")
} else {
if(eltSize!=1) {
TODO("multiply index by element size $eltSize ${target.position}")
// asmgen.loadScaledArrayIndexIntoRegister() ...
}
if(target.index.type.isByte) {
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
require(offset==0.toUByte())
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.out(" lda #$byte | sta ($zpPtrVar),y")
}
else {
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
require(offset==0.toUByte())
asmgen.out("""
pla
clc
adc $zpPtrVar+1
sta $zpPtrVar+1""")
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
asmgen.out(" lda #$byte | sta ($zpPtrVar),y")
}
}
val ptr = indexedEffectiveAddress(target)
asmgen.storeIndirectByte(byte, ptr, 0u)
}
internal fun assignWord(target: IndexedPtrTarget, word: Int) {
TODO("array ptr assign const word ${target.position}")
val ptr = indexedEffectiveAddress(target)
asmgen.storeIndirectWord(word, ptr, 0u)
}
internal fun assignLong(target: IndexedPtrTarget, long: Int) {
TODO("array ptr assign const long ${target.position}")
val ptr = indexedEffectiveAddress(target)
val hex = long.toLongHex()
asmgen.out("""
ldy #0
lda #$${hex.substring(6,8)}
sta ($ptr),y
iny
lda #$${hex.substring(4,6)}
sta ($ptr),y
iny
lda #$${hex.substring(2,4)}
sta ($ptr),y
iny
lda #$${hex.take(2)}
sta ($ptr),y""")
}
internal fun assignFloat(target: IndexedPtrTarget, float: Double) {
TODO("array ptr assign const float ${target.position}")
val ptr = indexedEffectiveAddress(target)
asmgen.storeIndirectFloat(float, ptr, 0u)
}
internal fun assignFAC1(target: IndexedPtrTarget) {
TODO("array ptr assign FAC1 ${target.position}")
val ptr = indexedEffectiveAddress(target)
asmgen.storeIndirectFloatFP1(ptr, 0u)
}
internal fun assignFloatAY(target: IndexedPtrTarget) {
TODO("array ptr assign float AY ${target.position}")
// A/Y contains a pointer to the source float. Save it, compute destination address,
// restore source pointer into W1, load destination pointer into A/Y, and copy.
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" pha | phy")
else
asmgen.out(" pha | tya | pha")
val ptr = indexedEffectiveAddress(target)
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out("""
ply
pla
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $ptr
ldy $ptr+1
jsr floats.copy_float""")
} else {
asmgen.out("""
pla
tay
pla
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $ptr
ldy $ptr+1
jsr floats.copy_float""")
}
}
internal fun assignFloatVar(target: IndexedPtrTarget, varName: String) {
TODO("array ptr assign float var ${target.position}")
val ptr = indexedEffectiveAddress(target)
asmgen.storeIndirectFloatVar(varName, ptr, 0u)
}
internal fun assignByteReg(target: IndexedPtrTarget, register: CpuRegister) {
// save register on the stack so indexedEffectiveAddress can freely use scratch regs
asmgen.saveRegisterStack(register, false)
TODO("array ptr assign byte reg ${target.position}")
asmgen.saveRegisterStack(register, false)
val ptr = indexedEffectiveAddress(target)
asmgen.restoreRegisterStack(register, false)
asmgen.storeIndirectByteReg(register, ptr, 0u, false, false)
}
internal fun assignWordReg(target: IndexedPtrTarget, regs: RegisterOrPair) {
// save word reg on the stack so indexedEffectiveAddress can freely use scratch regs
saveOnStack(regs)
TODO("array ptr assign word reg ${target.position}")
val ptr = indexedEffectiveAddress(target)
restoreFromStack(regs)
asmgen.storeIndirectWordReg(regs, ptr, 0u)
}
internal fun assignByteVar(target: IndexedPtrTarget, varName: String, extendToWord: Boolean, signed: Boolean) {
TODO("array ptr assign byte var ${target.position}")
val ptr = indexedEffectiveAddress(target)
if(extendToWord) {
// need to store byte var extended to word
asmgen.out(" lda $varName")
if(signed) asmgen.signExtendAXlsb(BaseDataType.BYTE) else asmgen.signExtendAXlsb(BaseDataType.UBYTE)
asmgen.storeIndirectWordReg(RegisterOrPair.AX, ptr, 0u)
} else {
asmgen.storeIndirectByteVar(varName, ptr, 0u)
}
}
internal fun assignWordVar(target: IndexedPtrTarget, varName: String) {
TODO("array ptr assign word var ${target.position}")
val ptr = indexedEffectiveAddress(target)
asmgen.storeIndirectWordVar(varName, DataType.UWORD, ptr, 0u)
}
internal fun assignLongVar(target: IndexedPtrTarget, varName: String) {
TODO("array ptr assign long var ${target.position}")
val ptr = indexedEffectiveAddress(target)
asmgen.out("""
ldy #0
lda $varName
sta ($ptr),y
iny
lda $varName+1
sta ($ptr),y
iny
lda $varName+2
sta ($ptr),y
iny
lda $varName+3
sta ($ptr),y""")
}
internal fun assignLongReg(target: IndexedPtrTarget, regs: RegisterOrPair) {
// generic fallback for long array via pointer
val startreg = regs.startregname()
// save source long on the stack in case index expression evaluation uses the same registers
asmgen.out("""
lda cx16.$startreg
pha
lda cx16.$startreg+1
pha
lda cx16.$startreg+2
pha
lda cx16.$startreg+3
pha""")
val ptr = indexedEffectiveAddress(target)
asmgen.out("""
pla
sta cx16.$startreg+3
pla
sta cx16.$startreg+2
pla
sta cx16.$startreg+1
pla
sta cx16.$startreg
ldy #0
lda cx16.$startreg
sta ($ptr),y
iny
lda cx16.$startreg+1
sta ($ptr),y
iny
lda cx16.$startreg+2
sta ($ptr),y
iny
lda cx16.$startreg+3
sta ($ptr),y""")
}
internal fun assignLongVar(pointer: PtPointerDeref, varName: String) {
@@ -2560,7 +2732,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer, arrayDt: DataType) {
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer) {
// Load value from array[index] and store to pointer target
if(target.datatype.isLong) {
// Get address of array[index] into AY, then 4-byte copy loop to target pointer
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,860 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen6502Internal
internal class TypeCastAssignmentsGen(
private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val pointergen: PointerAssignmentsGen
) {
lateinit var assignmentAsmGen: AssignmentAsmGen
internal fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: PtExpression, origTypeCastExpression: PtTypeCast) {
val valueDt = value.type
if(valueDt==targetDt)
throw AssemblyError("type cast to identical dt should have been removed: $valueDt at ${value.position}")
when(value) {
is PtIdentifier -> {
if(targetDt.isWord) {
if(valueDt.isUnsignedByte || valueDt.isBool) {
assignmentAsmGen.assignVariableUByteIntoWord(target, value)
return
}
if(valueDt.isSignedByte) {
assignmentAsmGen.assignVariableByteIntoWord(target, value)
return
}
}
}
is PtMemoryByte -> {
if(targetDt.isWord) {
fun assignViaExprEval(addressExpression: PtExpression) {
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
asmgen.out(" ldy #0")
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
when (value.address) {
is PtNumber -> {
val address = (value.address as PtNumber).number.toUInt()
assignmentAsmGen.assignMemoryByteIntoWord(target, address, null)
}
is PtIdentifier -> {
assignmentAsmGen.assignMemoryByteIntoWord(target, null, value.address as PtIdentifier)
}
is PtBinaryExpression -> {
val addrExpr = value.address as PtBinaryExpression
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, false)) {
asmgen.out(" ldy #0")
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
} else {
assignViaExprEval(value.address)
}
}
else -> {
assignViaExprEval(value.address)
}
}
return
}
}
is PtNumber -> {
if(targetDt.isPointer) {
// assign a number to a pointer type
require(valueDt.isInteger)
return assignmentAsmGen.assignConstantWord(target, value.number.toInt())
}
else
throw AssemblyError("literal value cast should have been const-folded away (target type=$targetDt)")
}
is PtBool -> throw AssemblyError("literal value cast should have been const-folded away (target type=$targetDt)")
is PtArrayIndexer -> {
if(targetDt.isByte && valueDt.isWord) {
// just assign the lsb from the array value
return assignCastWordViaLsbFunc(value, target)
} else if(targetDt.isBool && valueDt.isWord) {
return assignWordToBool(value, target)
}
}
else -> {}
}
// special case optimizations
if(target.kind == TargetStorageKind.VARIABLE) {
if(value is PtIdentifier && !valueDt.isUndefined)
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
when {
valueDt.isBool -> {
if(targetDt.isInteger) {
// optimization to assign boolean expression to integer target (just assign the 0 or 1 directly)
val assignDirect = AsmAssignment(
AsmAssignSource.fromAstSource(value, program, asmgen),
listOf(target),
program.memsizer,
target.position
)
assignmentAsmGen.assignExpression(assignDirect, target.scope)
} else {
TODO("assign bool to non-integer type $targetDt ${value.position}")
}
}
valueDt.isByte -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.A, valueDt.base, target.position)
}
valueDt.isLong -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.R14R15, valueDt.base, target.position)
}
valueDt.isWord || valueDt.isPointer -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, valueDt.isSigned)
assignTypeCastedRegisters(target.asmVarname, targetDt.base, RegisterOrPair.AY, valueDt.base, target.position)
}
valueDt.isFloat -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
assignTypeCastedFloatFAC1(target.asmVarname, targetDt.base)
}
valueDt.isPassByRef -> {
// str/array value cast (most likely to UWORD, take address-of)
assignmentAsmGen.assignExpressionToVariable(value, target.asmVarname, targetDt)
}
else -> throw AssemblyError("strange dt in typecast assign to var: $valueDt --> $targetDt")
}
return
}
if(valueDt.isWord && origTypeCastExpression.type.isUnsignedByte) {
val parentTc = origTypeCastExpression.parent as? PtTypeCast
if(parentTc!=null && parentTc.type.isUnsignedWord) {
// typecast a word value to ubyte and directly back to uword
// generate code for lsb(value) here instead of the ubyte typecast
return assignCastWordViaLsbFunc(value, target)
}
}
if(valueDt.isByteOrBool) {
when(target.register) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> {
// 'cast' an ubyte value to a byte register; no cast needed at all
return assignmentAsmGen.assignExpressionToRegister(value, target.register, valueDt.isSigned)
}
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, false)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
return
}
in CombinedLongRegisters -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, false)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, valueDt.isSigned, true)
return
}
else -> {}
}
} else if(valueDt.isWord) {
when(target.register) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> {
return if(targetDt.isBool)
assignWordToBool(value, target)
else
// cast an uword to a byte register, do this via lsb(value)
// generate code for lsb(value) here instead of the ubyte typecast
assignCastWordViaLsbFunc(value, target)
}
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// 'cast' uword into a 16 bits register, just assign it
return assignmentAsmGen.assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
}
RegisterOrPair.R0R1 -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R0, false)
return asmgen.signExtendLongVariable("cx16.r0", valueDt.base)
}
RegisterOrPair.R2R3 -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R2, false)
return asmgen.signExtendLongVariable("cx16.r2", valueDt.base)
}
RegisterOrPair.R4R5 -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R4, false)
return asmgen.signExtendLongVariable("cx16.r4", valueDt.base)
}
RegisterOrPair.R6R7 -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R6, false)
return asmgen.signExtendLongVariable("cx16.r6", valueDt.base)
}
RegisterOrPair.R8R9 -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R8, false)
return asmgen.signExtendLongVariable("cx16.r8", valueDt.base)
}
RegisterOrPair.R10R11 -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R10, false)
return asmgen.signExtendLongVariable("cx16.r10", valueDt.base)
}
RegisterOrPair.R12R13 -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R12, false)
return asmgen.signExtendLongVariable("cx16.r12", valueDt.base)
}
RegisterOrPair.R14R15 -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14, false)
return asmgen.signExtendLongVariable("cx16.r14", valueDt.base)
}
else -> {}
}
}
if(target.kind==TargetStorageKind.REGISTER) {
if(valueDt.isFloat && !target.datatype.isFloat) {
// have to typecast the float number on the fly down to an integer
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.FAC1, targetDt.isSigned)
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", targetDt.base)
assignmentAsmGen.assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, targetDt.isSigned, origTypeCastExpression.definingISub(), target.position)
return
} else {
if(!(valueDt isAssignableTo targetDt)) {
return if(valueDt.isWord && targetDt.isByte) {
// word to byte, just take the lsb
assignCastWordViaLsbFunc(value, target)
} else if(valueDt.isWord && targetDt.isBool) {
// word to bool
assignWordToBool(value, target)
} else if(valueDt.isWord && targetDt.isWord) {
// word to word, just assign
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
} else if(valueDt.isByteOrBool && targetDt.isByte) {
// byte to byte, just assign
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
} else if(valueDt.isByteOrBool && targetDt.isWord) {
// byte to word, just assign
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
} else if(valueDt.isLong && targetDt.isByte) {
// long to byte, just take the lsb
assignCastLongToByte(value, target)
} else if(valueDt.isLong && targetDt.isWord) {
// long to word, just take the lsw
assignCastViaLswFunc(value, target)
} else if(valueDt.isPointer && targetDt.isPointer) {
// pointer type A to pointer type B, just assign
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, valueDt.isSigned)
} else
throw AssemblyError("can't cast $valueDt to $targetDt, this should have been checked in the astchecker ${value.position}")
}
}
}
if(targetDt.isInteger && valueDt.isByteOrBool && valueDt.isAssignableTo(targetDt)) {
require(targetDt.isWord || targetDt.isLong) {
"should be byte to word or long assignment ${origTypeCastExpression.position}"
}
when(target.kind) {
// TargetStorageKind.VARIABLE -> {
// This has been handled already earlier on line 961.
// // byte to word, just assign to registers first, then assign to variable
// assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, targetDt==BaseDataType.WORD)
// assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, targetDt)
// return
// }
TargetStorageKind.ARRAY -> {
// byte to word, just assign to registers first, then assign into array
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, targetDt.isSigned)
val deref = target.array!!.pointerderef
if(deref!=null)
pointergen.assignWordReg(IndexedPtrTarget(target), RegisterOrPair.AY)
else
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
return
}
TargetStorageKind.REGISTER -> {
// byte to word or long, just assign to registers
assignmentAsmGen.assignExpressionToRegister(value, target.register!!, targetDt.isSigned)
return
}
TargetStorageKind.POINTER -> {
pointergen.assignByteToWord(PtrTarget(target), value)
return
}
else -> throw AssemblyError("weird target at ${target.position}")
}
}
if(targetDt.isFloat && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
if(target.register==RegisterOrPair.FAC2)
asmgen.pushFAC1()
when {
valueDt.isUnsignedByte -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.Y, false)
asmgen.out(" jsr floats.FREADUY")
}
valueDt.isSignedByte -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.A, true)
asmgen.out(" jsr floats.FREADSA")
}
valueDt.isUnsignedWord -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, false)
asmgen.out(" jsr floats.GIVUAYFAY")
}
valueDt.isSignedWord -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, true)
asmgen.out(" jsr floats.GIVAYFAY")
}
valueDt.isLong -> {
when(value) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(value)
asmgen.out("""
lda #<$varname
ldy #>$varname
jsr floats.internal_long_AY_to_FAC""")
}
else -> {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
asmgen.out("""
lda #<cx16.r14
ldy #>cx16.r14
jsr floats.internal_long_AY_to_FAC""")
}
}
}
else -> throw AssemblyError("invalid dt at ${target.position}")
}
if(target.register==RegisterOrPair.FAC2) {
asmgen.out(" jsr floats.MOVEF")
asmgen.popFAC1()
}
return
}
if(targetDt.isUnsignedWord && valueDt.isPointer) {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, false)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
return
}
// No more special optimized cases yet. Do the rest via more complex evaluation
// note: cannot use assignTypeCastedValue because that is ourselves :P
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
asmgen.assignExpressionTo(origTypeCastExpression, target)
}
internal fun assignCastLongToByte(value: PtExpression, target: AsmAssignTarget) {
// long to byte, can't use lsb() because that only works on words
when(value) {
is PtIdentifier -> {
val longvar = asmgen.asmVariableName(value)
asmgen.out(" lda $longvar")
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, true, false)
}
is PtNumber -> throw AssemblyError("casting a long number to byte should have been const-folded away ${value.position}")
else -> {
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.R14R15, true)
asmgen.out(" lda cx16.r14")
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, true, false)
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
}
}
}
internal fun assignCastWordViaLsbFunc(value: PtExpression, target: AsmAssignTarget) {
val lsb = PtFunctionCall("lsb", true, true,arrayOf(DataType.UBYTE), value.position)
lsb.parent = value.parent
lsb.add(value)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, listOf(target), program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign, value.definingISub())
}
internal fun assignCastViaLswFunc(value: PtExpression, target: AsmAssignTarget) {
val lsb = PtFunctionCall("lsw", true, true, arrayOf(DataType.UWORD), value.position)
lsb.parent = value.parent
lsb.add(value)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = lsb)
val assign = AsmAssignment(src, listOf(target), program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign, value.definingISub())
}
internal fun assignWordToBool(value: PtExpression, target: AsmAssignTarget) {
assignmentAsmGen.assignExpressionToRegister(value, RegisterOrPair.AY, false)
asmgen.out("""
sty P8ZP_SCRATCH_REG
ora P8ZP_SCRATCH_REG
beq +
lda #1
+""")
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
}
internal fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: BaseDataType) {
if(targetDt==BaseDataType.FLOAT)
throw AssemblyError("typecast to identical type")
when(targetDt) {
BaseDataType.BOOL -> asmgen.out(" jsr floats.cast_FAC1_as_bool_into_a | sta $targetAsmVarName")
BaseDataType.UBYTE -> asmgen.out(" jsr floats.cast_FAC1_as_uw_into_ya | sty $targetAsmVarName")
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName")
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_FAC1_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
BaseDataType.LONG -> {
asmgen.out("""
jsr floats.QINT
lda floats.FAC_ADDR+4
sta $targetAsmVarName
lda floats.FAC_ADDR+3
sta $targetAsmVarName+1
lda floats.FAC_ADDR+2
sta $targetAsmVarName+2
lda floats.FAC_ADDR+1
sta $targetAsmVarName+3
""".trimIndent())
}
else -> throw AssemblyError("weird type $targetDt")
}
}
internal fun assignTypeCastedIdentifier(targetAsmVarName: String, targetDt: DataType,
sourceAsmVarName: String, sourceDt: DataType) {
if(sourceDt == targetDt)
throw AssemblyError("typecast to identical type")
// also see: PtExpressionAsmGen, fun translateExpression(typecast: PtTypeCast)
when {
sourceDt.isUnsignedByte || sourceDt.isBool -> {
when(targetDt.base) {
BaseDataType.BOOL -> {
asmgen.out("""
lda $sourceAsmVarName
beq +
lda #1
+ sta $targetAsmVarName""")
}
BaseDataType.UBYTE, BaseDataType.BYTE -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
}
BaseDataType.UWORD, BaseDataType.WORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
BaseDataType.POINTER -> TODO("byte should not be cast to pointer")
BaseDataType.LONG -> {
asmgen.out("""
lda $sourceAsmVarName
sta $targetAsmVarName
lda #0
sta $targetAsmVarName+1
sta $targetAsmVarName+2
sta $targetAsmVarName+3""")
}
BaseDataType.FLOAT -> {
asmgen.out("""
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy $sourceAsmVarName
jsr floats.cast_from_ub""")
}
else -> throw AssemblyError("weird type $targetDt")
}
}
sourceDt.isSignedByte -> {
when(targetDt.base) {
BaseDataType.UBYTE, BaseDataType.BOOL -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
}
BaseDataType.UWORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
BaseDataType.WORD -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
}
BaseDataType.POINTER -> TODO("byte should not be cast to pointer")
BaseDataType.LONG -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
asmgen.signExtendVariableLsb(targetAsmVarName, BaseDataType.BYTE)
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
}
BaseDataType.FLOAT -> {
asmgen.out("""
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda $sourceAsmVarName
jsr floats.cast_from_b""")
}
else -> throw AssemblyError("weird type")
}
}
sourceDt.isUnsignedWord -> {
when(targetDt.base) {
BaseDataType.BOOL -> {
asmgen.out("""
lda $sourceAsmVarName
ora $sourceAsmVarName+1
beq +
lda #1
+ sta $targetAsmVarName""")
}
BaseDataType.BYTE, BaseDataType.UBYTE -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
}
BaseDataType.WORD -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
}
BaseDataType.POINTER -> asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
BaseDataType.LONG -> {
asmgen.out("""
lda $sourceAsmVarName
sta $targetAsmVarName
lda $sourceAsmVarName+1
sta $targetAsmVarName+1
lda #0
sta $targetAsmVarName+2
sta $targetAsmVarName+3""")
}
BaseDataType.FLOAT -> {
asmgen.out("""
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda $sourceAsmVarName
ldy $sourceAsmVarName+1
jsr floats.cast_from_uw""")
}
else -> throw AssemblyError("weird type")
}
}
sourceDt.isSignedWord || sourceDt.isPointer -> {
when(targetDt.base) {
BaseDataType.BOOL -> {
asmgen.out("""
lda $sourceAsmVarName
ora $sourceAsmVarName+1
beq +
lda #1
+ sta $targetAsmVarName""")
}
BaseDataType.BYTE, BaseDataType.UBYTE -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
}
BaseDataType.UWORD, BaseDataType.POINTER -> {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
}
BaseDataType.LONG -> {
asmgen.out("""
lda $sourceAsmVarName
sta $targetAsmVarName
lda $sourceAsmVarName+1
sta $targetAsmVarName+1""")
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
}
BaseDataType.FLOAT -> {
asmgen.out("""
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda $sourceAsmVarName
ldy $sourceAsmVarName+1
jsr floats.cast_from_w""")
}
else -> throw AssemblyError("weird type")
}
}
sourceDt.isFloat -> {
asmgen.out(" lda #<$sourceAsmVarName | ldy #>$sourceAsmVarName")
when(targetDt.base) {
BaseDataType.BOOL -> asmgen.out(" jsr floats.cast_as_bool_into_a | sta $targetAsmVarName")
BaseDataType.UBYTE -> asmgen.out(" jsr floats.cast_as_uw_into_ya | sty $targetAsmVarName")
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName")
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
BaseDataType.LONG -> {
asmgen.out("""
sta cx16.r0L
sty cx16.r0H
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
jsr floats.cast_as_long""")
}
else -> throw AssemblyError("weird type")
}
}
sourceDt.isString -> throw AssemblyError("cannot typecast a string value")
sourceDt.isLong -> {
if(targetDt.isByteOrBool) {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
} else if(targetDt.isWord || targetDt.isPointer) {
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda $sourceAsmVarName+1 | sta $targetAsmVarName+1")
} else if(targetDt.isFloat) {
asmgen.out("""
lda #<$sourceAsmVarName
ldy #>$sourceAsmVarName
sta cx16.r1L
sty cx16.r1H
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
jsr floats.internal_long_R1_to_float_AY""")
} else
throw AssemblyError("weird type")
}
else -> throw AssemblyError("weird type")
}
}
internal fun assignTypeCastedRegisters(targetAsmVarName: String, targetDt: BaseDataType,
regs: RegisterOrPair, sourceDt: BaseDataType, position: Position) {
if(sourceDt == targetDt && sourceDt != BaseDataType.POINTER)
throw AssemblyError("typecast to identical type")
// also see: PtExpressionAsmGen, fun translateExpression(typecast: PtTypeCast)
when(sourceDt) {
BaseDataType.BOOL -> {
if (targetDt.isByteOrBool) asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
else throw AssemblyError("assign bool to non-byte variable")
}
BaseDataType.UBYTE -> {
when(targetDt) {
BaseDataType.BOOL -> {
val compare = if(regs==RegisterOrPair.A) "cmp" else "cp${regs.toString().lowercase()}"
asmgen.out("""
$compare #0
beq +
ld${regs.toString().lowercase()} #1
+ st${regs.toString().lowercase()} $targetAsmVarName""")
}
BaseDataType.BYTE -> {
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
}
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> {
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
BaseDataType.LONG -> {
asmgen.out("""
st${regs.toString().lowercase()} $targetAsmVarName
lda #0
sta $targetAsmVarName+1
sta $targetAsmVarName+2
sta $targetAsmVarName+3""")
}
BaseDataType.FLOAT -> {
when(regs) {
RegisterOrPair.A -> asmgen.out(" tay")
RegisterOrPair.X -> asmgen.out(" txa | tay")
RegisterOrPair.Y -> {}
else -> throw AssemblyError("non-byte regs")
}
asmgen.out("""
lda #<$targetAsmVarName
sta P8ZP_SCRATCH_W2
lda #>$targetAsmVarName
sta P8ZP_SCRATCH_W2+1
jsr floats.cast_from_ub""")
}
else -> throw AssemblyError("weird type")
}
}
BaseDataType.BYTE -> {
when(targetDt) {
BaseDataType.BOOL -> TODO("assign byte to bool $position")
BaseDataType.UBYTE -> {
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
}
BaseDataType.UWORD -> {
if(asmgen.isTargetCpu(CpuType.CPU65C02))
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
}
BaseDataType.WORD, BaseDataType.POINTER -> {
when(regs) {
RegisterOrPair.A -> {}
RegisterOrPair.X -> asmgen.out(" txa")
RegisterOrPair.Y -> asmgen.out(" tya")
else -> throw AssemblyError("non-byte regs")
}
asmgen.signExtendAYlsb(sourceDt)
asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
}
BaseDataType.LONG -> {
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
asmgen.signExtendLongVariable(targetAsmVarName, sourceDt)
}
BaseDataType.FLOAT -> {
when(regs) {
RegisterOrPair.A -> {}
RegisterOrPair.X -> asmgen.out(" txa")
RegisterOrPair.Y -> asmgen.out(" tya")
else -> throw AssemblyError("non-byte regs")
}
asmgen.out("""
ldy #<$targetAsmVarName
sty P8ZP_SCRATCH_W2
ldy #>$targetAsmVarName
sty P8ZP_SCRATCH_W2+1
jsr floats.cast_from_b""")
}
else -> throw AssemblyError("weird type")
}
}
BaseDataType.UWORD -> {
when(targetDt) {
BaseDataType.BOOL -> TODO("assign uword to bool $position")
BaseDataType.BYTE, BaseDataType.UBYTE -> {
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
}
BaseDataType.WORD, BaseDataType.POINTER -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
else -> throw AssemblyError("non-word regs")
}
}
BaseDataType.LONG -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
else -> throw AssemblyError("non-word regs")
}
asmgen.out(" lda #0 | sta $targetAsmVarName+2 | sta $targetAsmVarName+3")
}
BaseDataType.FLOAT -> {
if(regs!=RegisterOrPair.AY)
throw AssemblyError("only supports AY here")
asmgen.out("""
tax
lda #<$targetAsmVarName
sta P8ZP_SCRATCH_W2
lda #>$targetAsmVarName
sta P8ZP_SCRATCH_W2+1
txa
jsr floats.cast_from_uw""")
}
else -> throw AssemblyError("weird type")
}
}
BaseDataType.WORD -> {
when(targetDt) {
BaseDataType.BOOL -> TODO("assign word to bool $position")
BaseDataType.BYTE, BaseDataType.UBYTE -> {
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
}
BaseDataType.UWORD, BaseDataType.POINTER -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
else -> throw AssemblyError("non-word regs")
}
}
BaseDataType.LONG -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
else -> throw AssemblyError("non-word regs")
}
asmgen.signExtendLongVariable(targetAsmVarName, BaseDataType.WORD)
}
BaseDataType.FLOAT -> {
if(regs!=RegisterOrPair.AY)
throw AssemblyError("only supports AY here")
asmgen.out("""
tax
lda #<$targetAsmVarName
sta P8ZP_SCRATCH_W2
lda #>$targetAsmVarName
sta P8ZP_SCRATCH_W2+1
txa
jsr floats.cast_from_w""")
}
else -> throw AssemblyError("weird type")
}
}
BaseDataType.STR -> throw AssemblyError("cannot typecast a string value")
BaseDataType.POINTER -> {
if(targetDt.isWord || targetDt.isPointer) {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
else -> throw AssemblyError("non-word regs")
}
} else if(targetDt.isLong) {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta $targetAsmVarName | stx $targetAsmVarName+1")
RegisterOrPair.AY -> asmgen.out(" sta $targetAsmVarName | sty $targetAsmVarName+1")
RegisterOrPair.XY -> asmgen.out(" stx $targetAsmVarName | sty $targetAsmVarName+1")
else -> throw AssemblyError("non-word regs")
}
asmgen.out(" lda #0 | sta $targetAsmVarName+2 | sta $targetAsmVarName+3")
} else {
throw AssemblyError("cannot assign pointer to $targetDt")
}
}
BaseDataType.LONG -> {
val startreg = regs.startregname()
if(targetDt.isByteOrBool) {
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName")
} else if(targetDt.isWord || targetDt.isPointer) {
asmgen.out(" lda cx16.$startreg | sta $targetAsmVarName | lda cx16.$startreg+1 | sta $targetAsmVarName+1")
} else if(targetDt.isFloat) {
if(regs==RegisterOrPair.R0R1)
throw AssemblyError("cannot assign long in R0R1 to float because r1 is used as a parameter $position")
asmgen.out("""
lda #<cx16.$startreg
ldy #>cx16.$startreg
sta cx16.r1L
sty cx16.r1H
lda #<$targetAsmVarName
ldy #>$targetAsmVarName
jsr floats.internal_long_R1_to_float_AY""")
} else
throw AssemblyError("weird type $targetDt")
}
else -> throw AssemblyError("weird type $sourceDt")
}
}
/**
* Compute 8-bit offset Y = idx*structSize + fieldOffset for a struct-array element.
* Assumes totalBytes <=256 so offset fits in Y. Uses A/Y and multiply_bytes when needed.
*/
}
+250 -55
View File
@@ -5,70 +5,73 @@ import io.kotest.matchers.shouldBe
import prog8.code.StMemVar
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8.codegen.cpu6502.*
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
class TestAsmOptimizer: FunSpec({
val machine = C64Target()
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val program = PtProgram("test", VMTarget())
val symbolTable = SymbolTable(program)
// Add test variables with known non-IO addresses so the optimizer can resolve them
// Non-IO addresses in typical C64 RAM range
symbolTable.add(StMemVar("myvar", DataType.UBYTE, 0x0300u, null, null))
symbolTable.add(StMemVar("var", DataType.UBYTE, 0x0301u, null, null))
symbolTable.add(StMemVar("var1", DataType.UWORD, 0x0302u, null, null))
symbolTable.add(StMemVar("var2", DataType.UWORD, 0x0304u, null, null))
symbolTable.add(StMemVar("ptr", DataType.UWORD, 0x0306u, null, null))
symbolTable.add(StMemVar("array", DataType.arrayFor(BaseDataType.UBYTE, machine), 0x0302u, 16u, null))
symbolTable.add(StMemVar("wordarray", DataType.arrayFor(BaseDataType.UWORD, machine), 0x0310u, 32u, null))
symbolTable.add(StMemVar("nswordarray", DataType.arrayFor(BaseDataType.UWORD, machine), 0x0320u, 32u, null))
symbolTable.add(StMemVar("var1", DataType.UWORD, 0x0400u, null, null))
symbolTable.add(StMemVar("var2", DataType.UWORD, 0x0402u, null, null))
symbolTable.add(StMemVar("ptr", DataType.UWORD, 0x0404u, null, null))
symbolTable.add(StMemVar("P8ZP_SCRATCH_PTR", DataType.UWORD, 0x02u, null, null))
symbolTable.add(StMemVar("P8ZP_SCRATCH_W1", DataType.UWORD, 0x04u, null, null))
symbolTable.add(StMemVar("A1", DataType.UWORD, 0x0310u, null, null))
symbolTable.add(StMemVar("A2", DataType.UWORD, 0x0312u, null, null))
symbolTable.add(StMemVar("A1", DataType.UWORD, 0x0410u, null, null))
symbolTable.add(StMemVar("A2", DataType.UWORD, 0x0412u, null, null))
fun optimize(lines: MutableList<String>): Int {
return optimizeAssembly(lines, machine, symbolTable)
fun optimize(lines: MutableList<String>) {
optimizeAssembly(lines, machine, symbolTable)
}
// --- optimizeIncDec ---
test("optimizeIncDec: removes iny+dey sequence") {
val lines = mutableListOf(" iny", " dey", " rts", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: removes iny+dey sequence with comments") {
val lines = mutableListOf(" iny ; increment Y", " dey ; decrement Y", " rts", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: removes inx+dex sequence") {
val lines = mutableListOf(" inx", " dex", " rts", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: removes dey+iny sequence") {
val lines = mutableListOf(" dey", " iny", " rts", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: removes dex+inx sequence") {
val lines = mutableListOf(" dex", " inx", " rts", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" rts", " nop")
}
test("optimizeIncDec: preserves non-canceling sequences") {
val lines = mutableListOf(" iny", " inx", " rts", " nop")
optimize(lines) shouldBe 0
optimize(lines)
lines shouldBe listOf(" iny", " inx", " rts", " nop")
}
@@ -76,42 +79,37 @@ class TestAsmOptimizer: FunSpec({
test("optimizeStoreLoadSame: removes sta X + lda X") {
val lines = mutableListOf(" lda #42", " sta myvar", " lda myvar", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" lda #42", " sta myvar", " rts")
}
test("optimizeStoreLoadSame: removes sta X + lda X with comments") {
// Note: comments don't affect the optimization, but the optimizer checks lines[1] and lines[2]
// and the comment is part of the line, so "sta myvar ; store it" doesn't match "sta myvar"
// The optimizer uses substring(4) which includes the comment
val lines = mutableListOf(" nop", " sta myvar", " lda myvar", " rts ; return")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" nop", " sta myvar", " rts ; return")
}
test("optimizeStoreLoadSame: removes pha + pla") {
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
val lines = mutableListOf(" nop", " pha", " pla", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" nop", " rts")
}
test("optimizeStoreLoadSame: removes phx + plx") {
val lines = mutableListOf(" nop", " phx", " plx", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" nop", " rts")
}
test("optimizeStoreLoadSame: removes phy + ply") {
val lines = mutableListOf(" nop", " phy", " ply", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" nop", " rts")
}
test("optimizeStoreLoadSame: removes lda X + sta X") {
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
val lines = mutableListOf(" nop", " lda myvar", " sta myvar", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" nop", " lda myvar", " rts")
}
@@ -119,77 +117,90 @@ class TestAsmOptimizer: FunSpec({
test("optimizeJsrRts: converts jsr+rts to jmp") {
val lines = mutableListOf(" jsr mysub", " rts", " nop", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" jmp mysub", " nop", " nop")
}
test("optimizeJsrRts: converts jsr+rts to jmp with comments") {
// Note: comments don't affect the optimization
val lines = mutableListOf(" jsr mysub ; call subroutine", " rts ; return", " nop", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" jmp mysub ; call subroutine", " nop", " nop")
}
test("optimizeJsrRts: removes rts+jmp") {
val lines = mutableListOf(" rts", " jmp somewhere", " nop", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" rts", " nop", " nop")
}
test("optimizeJsrRts: removes rts+bra") {
val lines = mutableListOf(" rts", " bra somewhere", " nop", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" rts", " nop", " nop")
}
test("optimizeJsrRts: removes lda+cmp #0") {
val lines = mutableListOf(" lda myvar", " cmp #0", " beq skip", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" lda myvar", " beq skip", " nop")
}
test("optimizeJsrRts: removes lda+cmp #0 with comments") {
val lines = mutableListOf(" lda myvar ; load variable", " cmp #0 ; compare to zero", " beq skip ; branch if equal", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" lda myvar ; load variable", " beq skip ; branch if equal", " nop")
}
test("optimizeJsrRts: removes bra+jmp") {
val lines = mutableListOf(" bra somewhere", " jmp elsewhere", " nop", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" bra somewhere", " nop", " nop")
}
test("optimizeJsrRts: preserves jsr+rts for floats.pushFAC") {
val lines = mutableListOf(" jsr floats.pushFAC", " rts", " nop", " nop")
optimize(lines) shouldBe 0
optimize(lines)
lines shouldBe listOf(" jsr floats.pushFAC", " rts", " nop", " nop")
}
test("optimizeJsrRts: three consecutive jsr only tail jsr+rts becomes jmp") {
val lines = mutableListOf($$" jsr $4000", $$" jsr $5000", $$" jsr $6000", " rts", " nop", " nop")
optimize(lines)
lines shouldBe listOf($$" jsr $4000", $$" jsr $5000", $$" jmp $6000", " nop", " nop")
}
test("optimizeJsrRts: single line with embedded newlines only tail converted defensively") {
val lines = mutableListOf($$" jsr $4000\n jsr $5000\n jsr $6000", " rts", " nop", " nop")
optimize(lines)
// Before fix this became jmp/jmp/jmp via replace("jsr","jmp"); after fix only one jmp via replaceMnemonic
val jmpCount = lines.joinToString("\n").split("jmp").size - 1
jmpCount shouldBe 1
}
// --- optimizeUselessPushPopStack ---
test("optimizeUselessPushPop: removes phy+ldy+pla") {
val lines = mutableListOf(" phy", " ldy #5", " pla", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" tya", " ldy #5", " rts")
}
test("optimizeUselessPushPop: removes phx+ldx+pla") {
val lines = mutableListOf(" phx", " ldx #5", " pla", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" txa", " ldx #5", " rts")
}
test("optimizeUselessPushPop: removes pha+lda+pla") {
// Note: optimizer removes all three: pha, lda, and pla
val lines = mutableListOf(" pha", " lda #5", " pla", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" rts")
}
test("optimizeUselessPushPop: removes pha+tya+tay+pla") {
val lines = mutableListOf(" pha", " tya", " tay", " pla", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" rts")
}
@@ -197,14 +208,14 @@ class TestAsmOptimizer: FunSpec({
test("optimizeUnneededTempvarInAdd: removes scratch variable in add") {
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" clc", " adc #5", " rts")
}
test("optimizeUnneededTempvarInAdd: removes scratch variable in add with comments") {
// Note: comments don't affect the optimization
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts ; return")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" clc", " adc #5", " rts ; return")
}
@@ -212,14 +223,14 @@ class TestAsmOptimizer: FunSpec({
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta") {
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts")
}
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta with comments") {
// Note: comments don't affect the optimization
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts ; return")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts ; return")
}
@@ -231,7 +242,7 @@ class TestAsmOptimizer: FunSpec({
" lda #1", " ldy #0", " sta var2", " sty var2+1",
" rts", " nop", " nop", " nop", " nop", " nop"
)
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(
" lda #1", " ldy #0", " sta var1", " sty var1+1",
" sta var2", " sty var2+1",
@@ -246,7 +257,7 @@ class TestAsmOptimizer: FunSpec({
" ldy #0", " lda (ptr),y", " ora #3", " ldy #0", " sta (ptr),y", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
)
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora #3", " sta (ptr),y", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
}
@@ -260,7 +271,7 @@ class TestAsmOptimizer: FunSpec({
" sta P8ZP_SCRATCH_PTR", " sty P8ZP_SCRATCH_PTR+1", " rts",
" nop", " nop", " nop", " nop"
)
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(
" clc", " adc P8ZP_SCRATCH_PTR", " sta P8ZP_SCRATCH_PTR",
" tya", " adc P8ZP_SCRATCH_PTR+1", " sta P8ZP_SCRATCH_PTR+1", " rts",
@@ -271,28 +282,201 @@ class TestAsmOptimizer: FunSpec({
test("optimizeExtraRegisterLoadInWordStore: removes ldx+sta+txa+ldy+sta") {
val lines = mutableListOf(" ldx #42", " sta myvar", " txa", " ldy #1", " sta myvar+1", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" sta myvar", " lda #42", " ldy #1", " sta myvar+1", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
}
// --- optimizeFoldIndexOffset (byte arrays) ---
test("optimizeFoldIndexOffset: folds byte array lda+clc+adc+tay+lda to ldy+lda") {
val lines = mutableListOf(
" lda myvar", " clc", " adc #3", " tay", " lda array,y",
" rts", " nop", " nop", " nop", " nop"
)
optimize(lines)
lines[0] shouldBe " ldy myvar"
lines[1] shouldBe " lda array+3,y"
lines.size shouldBe 7
}
test("optimizeFoldIndexOffset: folds with x register") {
val lines = mutableListOf(
" lda var", " clc", " adc #5", " tax", " lda array,x",
" rts", " nop", " nop", " nop", " nop"
)
optimize(lines)
lines[0] shouldBe " ldx var"
lines[1] shouldBe " lda array+5,x"
lines.size shouldBe 7
}
test("optimizeFoldIndexOffset: works with sta instead of lda") {
val lines = mutableListOf(
" lda myvar", " clc", " adc #1", " tay", " sta array,y",
" rts", " nop", " nop", " nop", " nop"
)
optimize(lines)
lines[0] shouldBe " ldy myvar"
lines[1] shouldBe " sta array+1,y"
lines.size shouldBe 7
}
test("optimizeFoldIndexOffset: does not fold indirect indexed (ptr),y") {
val lines = mutableListOf(
" lda myvar", " clc", " adc #3", " tay", " lda (ptr),y",
" rts", " nop", " nop", " nop", " nop"
)
optimize(lines)
lines.size shouldBe 10
}
test("optimizeFoldIndexOffset: preserves hex constant format") {
val lines = mutableListOf(
" lda myvar", " clc", " adc #$0f", " tay", " lda array,y",
" rts", " nop", " nop", " nop", " nop"
)
optimize(lines)
lines[0] shouldBe " ldy myvar"
lines[1] shouldBe " lda array+$0f,y"
lines.size shouldBe 7
}
test("optimizeFoldIndexOffset: does not fold when 5th op is not absolute indexed") {
val lines = mutableListOf(
" lda myvar", " clc", " adc #3", " tay", " nop",
" rts", " nop", " nop", " nop", " nop"
)
optimize(lines)
lines.size shouldBe 10
}
test("optimizeFoldIndexOffset: works with cmp instruction") {
val lines = mutableListOf(
" lda myvar", " clc", " adc #7", " tay", " cmp array,y",
" rts", " nop", " nop", " nop", " nop"
)
optimize(lines)
lines[0] shouldBe " ldy myvar"
lines[1] shouldBe " cmp array+7,y"
lines.size shouldBe 7
}
test("optimizeFoldIndexOffset: preserves label on first instruction") {
val lines = mutableListOf(
"mylabel: lda myvar", " clc", " adc #3", " tay", " lda array,y",
" rts", " nop", " nop", " nop", " nop"
)
optimize(lines)
lines[0] shouldBe "mylabel: ldy myvar"
lines[1] shouldBe " lda array+3,y"
lines.size shouldBe 7
}
// --- optimizeFoldIndexOffsetSplitWord (split word arrays) ---
test("optimizeFoldIndexOffsetSplitWord: folds split word array access with y") {
val lines = mutableListOf(
" lda var", " clc", " adc #3", " tay",
" lda wordarray_lsb,y", " ldx wordarray_msb,y",
" rts", " nop", " nop", " nop"
)
optimize(lines)
lines[0] shouldBe " ldy var"
lines[1] shouldBe " lda wordarray_lsb+3,y"
lines[2] shouldBe " ldx wordarray_msb+3,y"
lines.size shouldBe 7
}
test("optimizeFoldIndexOffsetSplitWord: folds split word array access with x") {
val lines = mutableListOf(
" lda var", " clc", " adc #3", " tax",
" lda wordarray_lsb,x", " ldx wordarray_msb,x",
" rts", " nop", " nop", " nop"
)
optimize(lines)
lines[0] shouldBe " ldx var"
lines[1] shouldBe " lda wordarray_lsb+3,x"
lines[2] shouldBe " ldx wordarray_msb+3,x"
lines.size shouldBe 7
}
test("optimizeFoldIndexOffsetSplitWord: does not fold mismatched registers") {
val lines = mutableListOf(
" lda var", " clc", " adc #3", " tay",
" lda wordarray_lsb,y", " ldx wordarray_msb,x",
" rts", " nop", " nop", " nop"
)
optimize(lines)
lines.size shouldBe 10
}
test("optimizeFoldIndexOffsetSplitWord: does not fold non-lsb-msb pair") {
val lines = mutableListOf(
" lda var", " clc", " adc #3", " tay",
" lda wordarray_lsb,y", " ldx other,y",
" rts", " nop", " nop", " nop"
)
optimize(lines)
lines.size shouldBe 10
}
// --- optimizeFoldIndexOffsetNonSplitWord (non-split word arrays) ---
test("optimizeFoldIndexOffsetNonSplitWord: folds non-split word array access") {
val lines = mutableListOf(
" lda var", " clc", " adc #3", " asl a", " tay",
" lda nswordarray,y", " ldx nswordarray+1,y",
" rts", " nop"
)
optimize(lines)
lines[0] shouldBe " lda var"
lines[1] shouldBe " asl a"
lines[2] shouldBe " tay"
lines[3] shouldBe " lda nswordarray+6,y"
lines[4] shouldBe " ldx nswordarray+1+6,y"
lines.size shouldBe 7
}
test("optimizeFoldIndexOffsetNonSplitWord: scales hex constants") {
val lines = mutableListOf(
" lda var", " clc", " adc #$02", " asl a", " tay",
" lda nswordarray,y", " ldx nswordarray+1,y",
" rts", " nop"
)
optimize(lines)
lines[3] shouldBe " lda nswordarray+$04,y"
lines[4] shouldBe " ldx nswordarray+1+$04,y"
lines.size shouldBe 7
}
test("optimizeFoldIndexOffsetNonSplitWord: does not fold without asl a") {
val lines = mutableListOf(
" lda var", " clc", " adc #3", " tay",
" lda nswordarray,y", " ldx nswordarray+1,y",
" rts", " nop", " nop"
)
optimize(lines)
lines.size shouldBe 9
}
// --- Edge cases ---
test("handles empty input") {
val lines = mutableListOf<String>()
optimize(lines) shouldBe 0
optimize(lines)
lines shouldBe emptyList()
}
test("handles single line input") {
val lines = mutableListOf(" rts")
optimize(lines) shouldBe 0
optimize(lines)
lines shouldBe listOf(" rts")
}
test("multiple optimizations in one pass") {
val lines = mutableListOf(" iny", " dey", " lda #1", " sta var", " lda var", " rts")
optimize(lines) shouldBe 2
optimize(lines)
lines shouldBe listOf(" lda #1", " sta var", " rts")
}
@@ -547,7 +731,9 @@ class TestAsmOptimizer: FunSpec({
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
)
// The second ldy should be removed since "ora" doesn't modify Y
optimize(lines) shouldBe 1
optimize(lines)
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora myvar", " sta (ptr),y", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
}
test("actual Y-modifying instructions should still be detected") {
@@ -557,7 +743,11 @@ class TestAsmOptimizer: FunSpec({
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
)
// The second ldy should NOT be removed since "tay" modifies Y
optimize(lines) shouldBe 0
optimize(lines)
lines shouldBe listOf(
" ldy #0", " lda (ptr),y", " tay",
" ldy #0", " sta (ptr),y", " rts",
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
}
test("modifiesYRegister: correctly identifies Y-modifying instructions") {
@@ -584,8 +774,13 @@ class TestAsmOptimizer: FunSpec({
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
" rts", " nop"
)
val count = optimize(lines)
count shouldBe 0
optimize(lines)
lines.size shouldBe 8 // all instructions preserved
lines shouldBe listOf(
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
" rts", " nop"
)
}
})
+16 -12
View File
@@ -12,10 +12,9 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.source.SourceCode
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8.codegen.cpu6502.AsmGen6502
import prog8.codegen.cpu6502.VariableAllocator
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.ErrorReporterForTests
import java.nio.file.Files
import kotlin.io.path.Path
@@ -46,13 +45,15 @@ class TestCodegen: FunSpec({
// xx += cx16.r0
// }
//}
val codegen = AsmGen6502(prefixSymbols = false, 0)
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val compTarget = C64Target()
val codegen = AsmGen6502(0)
val program = PtProgram("test", VMTarget())
val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
sub.add(PtVariable(
"pi",
DataType.UBYTE,
false,
ZeropageWish.DONTCARE,
0u,
false,
@@ -62,7 +63,8 @@ class TestCodegen: FunSpec({
))
sub.add(PtVariable(
"particleX",
DataType.arrayFor(BaseDataType.UBYTE),
DataType.arrayFor(BaseDataType.UBYTE, compTarget),
false,
ZeropageWish.DONTCARE,
0u,
false,
@@ -72,7 +74,8 @@ class TestCodegen: FunSpec({
))
sub.add(PtVariable(
"particleDX",
DataType.arrayFor(BaseDataType.UBYTE),
DataType.arrayFor(BaseDataType.UBYTE, compTarget),
false,
ZeropageWish.DONTCARE,
0u,
false,
@@ -83,6 +86,7 @@ class TestCodegen: FunSpec({
sub.add(PtVariable(
"xx",
DataType.WORD,
false,
ZeropageWish.DONTCARE,
0u,
false,
@@ -93,14 +97,14 @@ class TestCodegen: FunSpec({
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(false, Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY))
val targetIdx = PtArrayIndexer(DataType.UBYTE, false, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.arrayFor(BaseDataType.UBYTE, compTarget), Position.DUMMY))
idx.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY))
}
it.add(targetIdx)
}
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
value.add(PtIdentifier("main.start.particleDX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY))
val value = PtArrayIndexer(DataType.UBYTE, false, Position.DUMMY)
value.add(PtIdentifier("main.start.particleDX", DataType.arrayFor(BaseDataType.UBYTE, compTarget), Position.DUMMY))
value.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY))
assign.add(target)
assign.add(value)
@@ -166,11 +170,11 @@ class TestCodegen: FunSpec({
}
test("memory mapped zp var is correctly considered to be zp var") {
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
val program = PtProgram("test", VMTarget())
val st = SymbolTable(program)
st.add(StMemVar("zpmemvar", DataType.WORD, 0x20u, null, null))
st.add(StMemVar("normalmemvar", DataType.WORD, 0x9000u, null, null))
val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests())
val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests(), program)
allocator.isZpVar("zpmemvar") shouldBe true
allocator.isZpVar("normalmemvar") shouldBe false
}
-11
View File
@@ -1,11 +0,0 @@
plugins {
kotlin("jvm")
}
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
implementation(project(":intermediate"))
implementation(project(":codeGenIntermediate"))
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
}
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="codeGenIntermediate" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
</component>
</module>
@@ -1,46 +0,0 @@
package prog8.codegen.experimental
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRFileWriter
class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
override fun generate(
program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter
): IAssemblyProgram {
// you could write a code generator directly on the PtProgram AST,
// but you can also use the Intermediate Representation to build a codegen on:
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
val irProgram = irCodeGen.generate()
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
// this stub only writes the IR program to disk but doesn't generate anything else.
IRFileWriter(irProgram, null).write()
if(!options.quiet)
println("** experimental codegen stub: no assembly generated **")
return EmptyProgram
}
}
private object EmptyProgram : IAssemblyProgram {
override val name = "<Empty Program>"
override val irInstructionCount: Int = 0
override val irChunkCount: Int = 0
override val irRegisterCount: Int = 0
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
if(!options.quiet)
println("** nothing assembled **")
return true
}
}
-1
View File
@@ -11,5 +11,4 @@ dependencies {
testImplementation(testFixtures(project(":codeCore")))
testImplementation("io.kotest:kotest-runner-junit5")
testImplementation("io.kotest:kotest-framework-datatest")
}
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="module" module-name="intermediate" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
File diff suppressed because it is too large. Load diff
@@ -81,7 +81,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
addInstr(result, IRInstructions.push(IRDataType.BYTE, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -89,7 +89,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
addInstr(result, IRInstructions.push(IRDataType.WORD, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -97,7 +97,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.LONG, reg1=tr.resultReg), null)
addInstr(result, IRInstructions.push(IRDataType.LONG, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -105,35 +105,35 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.PUSH, IRDataType.FLOAT, fpReg1 = RegisterNum(tr.resultFpReg)), null)
addInstr(result, IRInstructions.push(IRDataType.FLOAT, tr.resultFpReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPop(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=resultRegister), null)
addInstr(result, IRInstructions.pop(IRDataType.BYTE, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
private fun funcPopW(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=resultRegister), null)
addInstr(result, IRInstructions.pop(IRDataType.WORD, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
private fun funcPopL(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.LONG, reg1=resultRegister), null)
addInstr(result, IRInstructions.pop(IRDataType.LONG, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
private fun funcPopF(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.POP, IRDataType.FLOAT, fpReg1 = RegisterNum(resultRegister)), null)
addInstr(result, IRInstructions.pop(IRDataType.FLOAT, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, resultRegister)
}
@@ -147,15 +147,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
IRDataType.LONG -> {
val intermediateReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, type, reg1=intermediateReg, reg2=trSourceAddr.resultReg, immediate = 0)
it += IRInstruction(Opcode.STOREI, type, reg1=intermediateReg, reg2=trTargetAddr.resultReg, immediate = 0)
it += IRInstructions.loadMemory(Opcode.LOADI, type, intermediateReg, IRMemory.indirect(trSourceAddr.resultReg))
it += IRInstructions.storeMemory(Opcode.STOREI, type, intermediateReg, IRMemory.indirect(trTargetAddr.resultReg))
}
}
IRDataType.FLOAT -> {
val intermediateReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, type, fpReg1=RegisterNum(intermediateReg), reg1=trSourceAddr.resultReg, immediate = 0)
it += IRInstruction(Opcode.STOREI, type, fpReg1=RegisterNum(intermediateReg), reg1=trTargetAddr.resultReg, immediate = 0)
it += IRInstructions.loadMemory(Opcode.LOADI, type, intermediateReg, IRMemory.indirect(trSourceAddr.resultReg))
it += IRInstructions.storeMemory(Opcode.STOREI, type, intermediateReg, IRMemory.indirect(trTargetAddr.resultReg))
}
}
else -> throw AssemblyError("invalid type $type")
@@ -169,12 +169,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
return if(resultType==IRDataType.FLOAT) {
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(valueTr.resultFpReg)), null)
addInstr(result, IRInstructions.binary(Opcode.SQUARE, resultType, resultFpReg, valueTr.resultFpReg), null)
ExpressionCodeResult(result, resultType, -1, resultFpReg)
}
else {
val resultReg = codeGen.registers.next(resultType)
addInstr(result, IRInstruction(Opcode.SQUARE, resultType, reg1 = resultReg, reg2 = valueTr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.SQUARE, resultType, resultReg, valueTr.resultReg), null)
ExpressionCodeResult(result, resultType, resultReg, -1)
}
}
@@ -183,12 +183,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
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)
addInstr(result, IRInstructions.call(Opcode.CALLI, CallSite(CallTarget.Direct(codeIndirect(addressTr.resultReg)))), null)
return if(call.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else {
val resultvalueReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=resultvalueReg), null)
val slot = if(codeGen.options.compTarget.cpu.usesM68kConvention) 10 else 4
addInstr(result, IRInstructions.hardwareLoad(IRDataType.WORD, resultvalueReg, CallingConventionSlot(slot)), null)
ExpressionCodeResult(result, IRDataType.WORD, resultvalueReg, -1)
}
}
@@ -201,8 +202,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, bankTr, bankTr.resultReg, -1)
addToResult(result, addressTr, addressTr.resultReg, -1)
addToResult(result, argumentwordTr, argumentwordTr.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, IRDataType.WORD to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to argumentwordTr.resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, argumentwordTr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
result += codeGen.makeSyscall(IMSyscall.CALLFAR, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg, IRDataType.WORD to argumentwordTr.resultReg), IRDataType.WORD to resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcCallfar2(call: PtFunctionCall): ExpressionCodeResult {
@@ -219,15 +221,20 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, argumentX, argumentX.resultReg, -1)
addToResult(result, argumentY, argumentY.resultReg, -1)
addToResult(result, argumentCarry, argumentCarry.resultReg, -1)
result += codeGen.makeSyscall(IMSyscall.CALLFAR2, listOf(IRDataType.BYTE to bankTr.resultReg, IRDataType.WORD to addressTr.resultReg,
val resultReg = codeGen.registers.next(IRDataType.WORD)
result += codeGen.makeSyscall(IMSyscall.CALLFAR2, listOf(IRDataType.BYTE to bankTr.resultReg, codeGen.addressDt to addressTr.resultReg,
IRDataType.BYTE to argumentA.resultReg,
IRDataType.BYTE to argumentX.resultReg,
IRDataType.BYTE to argumentY.resultReg,
IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to addressTr.resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, addressTr.resultReg, -1)
IRDataType.BYTE to argumentCarry.resultReg), IRDataType.WORD to resultReg)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcDivmod(call: PtFunctionCall, type: IRDataType, signed: Boolean): ExpressionCodeResult {
if(signed && codeGen.options.compTarget.cpu.is6502) {
codeGen.errors.err("expected all ubyte or all uword arguments (no signed divmod support on 6502 yet)", call.position)
return ExpressionCodeResult(mutableListOf(), type, -1, -1)
}
val result = mutableListOf<IRCodeChunkBase>()
val number = call.args[0]
val divident = call.args[1]
@@ -238,33 +245,48 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(divident is PtNumber) {
val tr = exprGen.translateExpression(number)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(divmodOpcode, type, reg1 = tr.resultReg, immediate = divident.number.toInt()), null)
divisionReg = tr.resultReg
remainderReg = codeGen.registers.next(type)
addInstr(result, IRInstructions.divmodImmediate(divmodOpcode, type, tr.resultReg, remainderReg, divident.number.toInt()), null)
divisionReg = tr.resultReg
} else {
val numTr = exprGen.translateExpression(number)
addToResult(result, numTr, numTr.resultReg, -1)
val dividentTr = exprGen.translateExpression(divident)
addToResult(result, dividentTr, dividentTr.resultReg, -1)
addInstr(result, IRInstruction(divmodrOpcode, type, reg1 = numTr.resultReg, reg2=dividentTr.resultReg), null)
divisionReg = numTr.resultReg
remainderReg = dividentTr.resultReg
addInstr(result, IRInstructions.divmodRegister(divmodrOpcode, type, numTr.resultReg, dividentTr.resultReg), null)
divisionReg = numTr.resultReg
}
// DIVMOD result convention: quotient in reg1, remainder in reg2.
// Route to the return value locations expected by the caller (via returnsWhatWhere).
if(codeGen.options.compTarget.cpu.usesM68kConvention) {
// m68k: first return value in D0 (slot 10), second in D1 (slot 11)
if(type==IRDataType.BYTE) {
result += IRCodeChunk(null, null).also {
it += IRInstructions.hardwareStore(IRDataType.BYTE, remainderReg, CallingConventionSlot(11))
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(10))
}
} else if(type==IRDataType.WORD) {
result += IRCodeChunk(null, null).also {
it += IRInstructions.hardwareStore(IRDataType.WORD, remainderReg, CallingConventionSlot(11))
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(10))
}
} else throw AssemblyError("invalid type for DIVMOD")
} else {
// 6502/cx16: quotient to A (slot 0) or AY (slot 4), remainder to cx16.r15
// Note: STOREM must come before STOREHR, otherwise STOREM clobbers A before LOADHR reads it.
if(type==IRDataType.BYTE) {
result += IRCodeChunk(null, null).also {
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, remainderReg, IRMemory.direct("cx16.r15"))
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(0))
}
} else if(type==IRDataType.WORD) {
result += IRCodeChunk(null, null).also {
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, remainderReg, IRMemory.direct("cx16.r15"))
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(4))
}
} else throw AssemblyError("invalid type for DIVMOD")
}
// DIVMOD instruction result convention: on value stack, division and remainder on top.
addInstr(result, IRInstruction(Opcode.POP, type, reg1=remainderReg), null)
addInstr(result, IRInstruction(Opcode.POP, type, reg1=divisionReg), null)
// return the 2 result values: division and remainder division in AY, remainder in R15
if(type==IRDataType.BYTE) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=divisionReg)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=remainderReg, labelSymbol = "cx16.r15")
}
} else if(type==IRDataType.WORD) {
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=divisionReg)
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=remainderReg, labelSymbol = "cx16.r15")
}
} else throw AssemblyError("invalid type for DIVMOD")
return ExpressionCodeResult(result, type, -1, -1)
}
@@ -275,7 +297,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, left, left.resultReg, -1)
addToResult(result, right, right.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += codeGen.makeSyscall(IMSyscall.COMPARE_STRINGS, listOf(IRDataType.WORD to left.resultReg, IRDataType.WORD to right.resultReg), IRDataType.BYTE to resultReg)
val addressDt = codeGen.addressDt
result += codeGen.makeSyscall(IMSyscall.COMPARE_STRINGS, listOf(addressDt to left.resultReg, addressDt to right.resultReg), IRDataType.BYTE to resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -285,9 +308,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, leftTr, leftTr.resultReg, -1)
val rightTr = exprGen.translateExpression(call.args[1])
addToResult(result, rightTr, rightTr.resultReg, -1)
val dt = irType(call.args[0].type)
val dt = codeGen.irType(call.args[0].type)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMP, dt, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
it += IRInstructions.compare(dt, leftTr.resultReg, rightTr.resultReg)
}
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
}
@@ -300,14 +323,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
val needsExplicitCmpi = !codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
when (sourceDt.base) {
BaseDataType.BYTE -> {
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
it += IRInstructions.move(IRDataType.BYTE, compareReg, tr.resultReg)
if(needsExplicitCmpi)
it += IRInstructions.compareImmediate(IRDataType.BYTE, compareReg, 0)
it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel))
it += IRInstructions.unary(Opcode.NEG, IRDataType.BYTE, tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
@@ -316,9 +342,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
it += IRInstructions.move(IRDataType.WORD, compareReg, tr.resultReg)
if(needsExplicitCmpi)
it += IRInstructions.compareImmediate(IRDataType.WORD, compareReg, 0)
it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel))
it += IRInstructions.unary(Opcode.NEG, IRDataType.WORD, tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.WORD, tr.resultReg, -1)
@@ -327,18 +355,24 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val notNegativeLabel = codeGen.createLabelName()
val compareReg = codeGen.registers.next(IRDataType.LONG)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
it += IRInstructions.move(IRDataType.LONG, compareReg, tr.resultReg)
if(needsExplicitCmpi)
it += IRInstructions.compareImmediate(IRDataType.LONG, compareReg, 0)
it += IRInstructions.branch(Opcode.BSTPOS, codeLabel(notNegativeLabel))
it += IRInstructions.unary(Opcode.NEG, IRDataType.LONG, tr.resultReg)
}
result += IRCodeChunk(notNegativeLabel, null)
return ExpressionCodeResult(result, IRDataType.LONG, tr.resultReg, -1)
}
BaseDataType.FLOAT -> {
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.FABS, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg)), null)
addInstr(result, IRInstructions.binary(Opcode.FABS, IRDataType.FLOAT, resultFpReg, tr.resultFpReg), null)
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
BaseDataType.POINTER -> {
// pointer is unsigned, abs has no effect
return ExpressionCodeResult(result, IRDataType.POINTER, tr.resultReg, -1)
}
else -> throw AssemblyError("weird dt")
}
}
@@ -351,12 +385,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(tr.dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, fpReg1 = RegisterNum(tr.resultFpReg))
it += IRInstructions.binary(Opcode.SGN, tr.dt, resultReg, tr.resultFpReg)
}
} else {
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SGN, tr.dt, reg1 = resultReg, reg2 = tr.resultReg)
it += IRInstructions.binary(Opcode.SGN, tr.dt, resultReg, tr.resultReg)
}
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
@@ -371,7 +405,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.BYTE, reg1=resultReg, reg2=tr.resultReg)
it += IRInstructions.binary(Opcode.SQRT, IRDataType.BYTE, resultReg, tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -379,7 +413,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE) // sqrt of a word still produces just a byte result
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.WORD, reg1=resultReg, reg2=tr.resultReg)
it += IRInstructions.binary(Opcode.SQRT, IRDataType.WORD, resultReg, tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -387,7 +421,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD) // sqrt of a long still produces just a word result
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.LONG, reg1=resultReg, reg2=tr.resultReg)
it += IRInstructions.binary(Opcode.SQRT, IRDataType.LONG, resultReg, tr.resultReg)
}
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
@@ -395,7 +429,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, -1, tr.resultFpReg)
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.SQRT, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg), fpReg2 = RegisterNum(tr.resultFpReg))
it += IRInstructions.binary(Opcode.SQRT, IRDataType.FLOAT, resultFpReg, tr.resultFpReg)
}
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
@@ -410,13 +444,13 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
// msb is 0, use EXT
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=resultReg, reg2 = lsbTr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, resultReg, lsbTr.resultReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
addToResult(result, msbTr, msbTr.resultReg, -1)
val lsbTr = exprGen.translateExpression(call.args[1])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=resultReg, reg2 = msbTr.resultReg, reg3 = lsbTr.resultReg), null)
addInstr(result, IRInstructions.concat(IRDataType.BYTE, resultReg, msbTr.resultReg, lsbTr.resultReg), null)
}
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
@@ -430,23 +464,21 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
// msw is 0, use EXT
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = lswTr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.WORD, resultReg, lswTr.resultReg), null)
} else {
val mswTr = exprGen.translateExpression(call.args[0])
addToResult(result, mswTr, mswTr.resultReg, -1)
val lswTr = exprGen.translateExpression(call.args[1])
addToResult(result, lswTr, lswTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswTr.resultReg, reg3 = lswTr.resultReg), null)
addInstr(result, IRInstructions.concat(IRDataType.WORD, resultReg, mswTr.resultReg, lswTr.resultReg), null)
}
} else {
// mklong(msb, b3, b2, lsb)
if((call.args[0] as? PtNumber)?.number == 0.0 && (call.args[1] as? PtNumber)?.number == 0.0 && (call.args[2] as? PtNumber)?.number == 0.0) {
// use EXT.b + EXT.w
// use EXTL.b: zero-extend byte directly to long
val lsbTr = exprGen.translateExpression(call.args[3])
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val wordReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=wordReg, reg2 = lsbTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.WORD, reg1=resultReg, reg2 = wordReg), null)
addInstr(result, IRInstructions.binary(Opcode.EXTL, IRDataType.BYTE, resultReg, lsbTr.resultReg), null)
} else {
val msbTr = exprGen.translateExpression(call.args[0])
val b2Tr = exprGen.translateExpression(call.args[1])
@@ -458,9 +490,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, lsbTr, lsbTr.resultReg, -1)
val lswReg = codeGen.registers.next(IRDataType.WORD)
val mswReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=mswReg, reg2 = msbTr.resultReg, reg3 = b2Tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=lswReg, reg2 = b1Tr.resultReg, reg3 = lsbTr.resultReg), null)
addInstr(result, IRInstruction(Opcode.CONCAT, IRDataType.WORD, reg1=resultReg, reg2 = mswReg, reg3 = lswReg), null)
addInstr(result, IRInstructions.concat(IRDataType.BYTE, mswReg, msbTr.resultReg, b2Tr.resultReg), null)
addInstr(result, IRInstructions.concat(IRDataType.BYTE, lswReg, b1Tr.resultReg, lsbTr.resultReg), null)
addInstr(result, IRInstructions.concat(IRDataType.WORD, resultReg, mswReg, lswReg), null)
}
}
@@ -468,44 +500,35 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
private fun funcClamp(call: PtFunctionCall): ExpressionCodeResult {
val type = codeGen.irType(call.type)
require(type != IRDataType.FLOAT)
val result = mutableListOf<IRCodeChunkBase>()
val type = irType(call.type)
val valueTr = exprGen.translateExpression(call.args[0])
val minimumTr = exprGen.translateExpression(call.args[1])
val maximumTr = exprGen.translateExpression(call.args[2])
result += valueTr.chunks
result += minimumTr.chunks
result += maximumTr.chunks
if(type==IRDataType.FLOAT) {
result += codeGen.makeSyscall(
IMSyscall.CLAMP_FLOAT, listOf(
valueTr.dt to valueTr.resultFpReg,
minimumTr.dt to minimumTr.resultFpReg,
maximumTr.dt to maximumTr.resultFpReg,
), type to valueTr.resultFpReg
)
return ExpressionCodeResult(result, type, -1, valueTr.resultFpReg)
} else {
val syscall = when(call.type.base) {
BaseDataType.UBYTE -> IMSyscall.CLAMP_UBYTE
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
else -> throw AssemblyError("invalid dt")
}
result += codeGen.makeSyscall(syscall, listOf(
valueTr.dt to valueTr.resultReg,
minimumTr.dt to minimumTr.resultReg,
maximumTr.dt to maximumTr.resultReg,
), type to valueTr.resultReg
)
return ExpressionCodeResult(result, type, valueTr.resultReg, -1)
val syscall = when(call.type.base) {
BaseDataType.UBYTE -> IMSyscall.CLAMP_UBYTE
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
else -> throw AssemblyError("invalid dt")
}
val resultReg = codeGen.registers.next(type)
result += codeGen.makeSyscall(syscall, listOf(
valueTr.dt to valueTr.resultReg,
minimumTr.dt to minimumTr.resultReg,
maximumTr.dt to maximumTr.resultReg,
), type to resultReg
)
return ExpressionCodeResult(result, type, resultReg, -1)
}
private fun funcMin(call: PtFunctionCall): ExpressionCodeResult {
val type = irType(call.type)
val type = codeGen.irType(call.type)
val result = mutableListOf<IRCodeChunkBase>()
val leftTr = exprGen.translateExpression(call.args[0])
addToResult(result, leftTr, leftTr.resultReg, -1)
@@ -514,17 +537,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val comparisonOpcode = if(call.type.isSigned) Opcode.BGTSR else Opcode.BGTR
val after = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(comparisonOpcode, type, reg1 = rightTr.resultReg, reg2 = leftTr.resultReg, labelSymbol = after)
it += IRInstructions.branchRegister(comparisonOpcode, type, rightTr.resultReg, leftTr.resultReg, codeLabel(after))
// right <= left, take right
it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
it += IRInstructions.move(type, leftTr.resultReg, rightTr.resultReg)
it += IRInstructions.jump(codeLabel(after))
}
result += IRCodeChunk(after, null)
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
}
private fun funcMax(call: PtFunctionCall): ExpressionCodeResult {
val type = irType(call.type)
val type = codeGen.irType(call.type)
val result = mutableListOf<IRCodeChunkBase>()
val leftTr = exprGen.translateExpression(call.args[0])
addToResult(result, leftTr, leftTr.resultReg, -1)
@@ -533,33 +556,35 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val comparisonOpcode = if(call.type.isSigned) Opcode.BGTSR else Opcode.BGTR
val after = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(comparisonOpcode, type, reg1 = leftTr.resultReg, reg2 = rightTr.resultReg, labelSymbol = after)
it += IRInstructions.branchRegister(comparisonOpcode, type, leftTr.resultReg, rightTr.resultReg, codeLabel(after))
// right >= left, take right
it += IRInstruction(Opcode.LOADR, type, reg1=leftTr.resultReg, reg2=rightTr.resultReg)
it += IRInstruction(Opcode.JUMP, labelSymbol = after)
it += IRInstructions.move(type, leftTr.resultReg, rightTr.resultReg)
it += IRInstructions.jump(codeLabel(after))
}
result += IRCodeChunk(after, null)
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
}
private fun funcPoke(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult {
// Try struct-array field folding: pokew(&arr[idx]+fieldOff, value) -> STOREX arr+fieldOff,S=structSize
tryFoldStructArrayPoke(call, dt)?.let { return it }
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toUInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, dt, address = address.toAddress())
it += IRInstructions.storeZero(Opcode.STOREZM, dt, IRMemory.direct(address.toAddress()))
}
} else {
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
if(address!=null) {
val tr = exprGen.translateExpression(address)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = offset!!), null)
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, dt, IRMemory.indirect(tr.resultReg, offset!!)), null)
} else {
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg, immediate = 0), null)
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, dt, IRMemory.indirect(tr.resultReg)), null)
}
}
} else {
@@ -568,9 +593,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val address = (call.args[0] as PtNumber).number.toUInt()
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
if(dt==IRDataType.FLOAT) {
addInstr(result, IRInstruction(Opcode.STOREM, dt, fpReg1 = RegisterNum(valueTr.resultFpReg), address = address.toAddress()), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, dt, valueTr.resultFpReg, IRMemory.direct(address.toAddress())), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREM, dt, reg1 = valueTr.resultReg, address = address.toAddress()), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, dt, valueTr.resultReg, IRMemory.direct(address.toAddress())), null)
}
} else {
val (address, offset) = exprGen.getAddressAndOffset(call.args[0])
@@ -579,18 +604,18 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
addToResult(result, addressTr, addressTr.resultReg, -1)
if(dt==IRDataType.FLOAT) {
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = offset!!), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueTr.resultFpReg, IRMemory.indirect(addressTr.resultReg, offset!!)), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = offset!!), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, dt, valueTr.resultReg, IRMemory.indirect(addressTr.resultReg, offset!!)), null)
}
} else {
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
addToResult(result, addressTr, addressTr.resultReg, -1)
if(dt==IRDataType.FLOAT) {
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = RegisterNum(valueTr.resultFpReg), immediate = 0), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueTr.resultFpReg, IRMemory.indirect(addressTr.resultReg)), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg, immediate = 0), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, dt, valueTr.resultReg, IRMemory.indirect(addressTr.resultReg)), null)
}
}
}
@@ -604,26 +629,26 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if(call.args[0] is PtNumber) {
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
val address = (call.args[0] as PtNumber).number.toUInt()
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), address = address.toAddress()), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.FLOAT, resultFpRegister, IRMemory.direct(address.toAddress())), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultFpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = RegisterNum(resultFpReg), immediate = 0), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, resultFpReg, IRMemory.indirect(tr.resultReg)), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
} else {
if (call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.next(dt)
val address = (call.args[0] as PtNumber).number.toUInt()
addInstr(result, IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address.toAddress()), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, dt, resultRegister, IRMemory.direct(address.toAddress())), null)
ExpressionCodeResult(result, dt, resultRegister, -1)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(dt)
addInstr(result, IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg, immediate = 0), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, dt, resultReg, IRMemory.indirect(tr.resultReg)), null)
ExpressionCodeResult(result, dt, resultReg, -1)
}
}
@@ -640,11 +665,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val addressNum = address.number.toUInt()
val resultRegister = codeGen.registers.next(IRDataType.BYTE)
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = addressNum.toAddress()), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADM, IRDataType.BYTE, resultRegister, IRMemory.direct(addressNum.toAddress())), null)
if (newValueTr == null) {
addInstr(result, IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = addressNum.toAddress()), null)
addInstr(result, IRInstructions.storeZero(Opcode.STOREZM, IRDataType.BYTE, IRMemory.direct(addressNum.toAddress())), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newValueTr.resultReg, address = addressNum.toAddress()), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, newValueTr.resultReg, IRMemory.direct(addressNum.toAddress())), null)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
@@ -657,11 +682,11 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
if (newValueTr != null) addToResult(result, newValueTr, newValueTr.resultReg, -1)
addToResult(result, addressTr, addressTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.BYTE, resultReg, IRMemory.indirect(addressTr.resultReg, finalOffset)), null)
if (newValueTr == null) {
addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = addressTr.resultReg, immediate = finalOffset), null)
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(addressTr.resultReg, finalOffset)), null)
} else {
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = newValueTr.resultReg, reg2 = addressTr.resultReg, immediate = finalOffset), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, newValueTr.resultReg, IRMemory.indirect(addressTr.resultReg, finalOffset)), null)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -670,10 +695,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun funcStructAlloc(call: PtFunctionCall): ExpressionCodeResult {
val code = IRCodeChunk(null, null)
val resultReg = codeGen.registers.next(IRDataType.WORD)
val resultReg = codeGen.registers.next(IRDataType.POINTER)
val labelname = SymbolTable.labelnameForStructInstance(call)
code += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultReg, labelSymbol = "${StStructInstanceBlockName}.$labelname")
return ExpressionCodeResult(code, IRDataType.WORD, resultReg, -1)
code += IRInstructions.loadAddress(IRDataType.POINTER, resultReg, "${StStructInstanceBlockName}.$labelname")
return ExpressionCodeResult(code, IRDataType.POINTER, resultReg, -1)
}
private fun funcLsb(call: PtFunctionCall, fromLong: Boolean): ExpressionCodeResult {
@@ -682,9 +707,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
if(fromLong)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, resultReg, tr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, resultReg, tr.resultReg), null)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -694,14 +719,25 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
// TODO this can be more optimal if the argument is a variable or memory address
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
// low byte returned in A, mid in R15, high (bank) in R14
val byteReg = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.BSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r14"), null)
addInstr(result, IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r15"), null)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1 = byteReg), null)
if(codeGen.options.compTarget.cpu.usesM68kConvention) {
// m68k: 3 return values in D0 (slot 10), D1 (slot 11), D2 (slot 12)
// store high byte first, then mid, then low (to avoid clobbering)
addInstr(result, IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(12)), null)
addInstr(result, IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(11)), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(10)), null)
} else {
// 6502/cx16: low byte in A (slot 0), mid in cx16.r15, high (bank) in cx16.r14
addInstr(result, IRInstructions.binary(Opcode.BSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, byteReg, IRMemory.direct("cx16.r14")), null)
addInstr(result, IRInstructions.binary(Opcode.MIDB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, byteReg, IRMemory.direct("cx16.r15")), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, byteReg, tr.resultReg), null)
addInstr(result, IRInstructions.hardwareStore(IRDataType.BYTE, byteReg, CallingConventionSlot(0)), null)
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
@@ -710,7 +746,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, resultReg, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
@@ -720,9 +756,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
if(fromLong)
addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.MSIGB, IRDataType.LONG, resultReg, tr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.MSIGB, IRDataType.WORD, resultReg, tr.resultReg), null)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -732,14 +768,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.MSIGW, IRDataType.LONG, reg1 = resultReg, reg2 = tr.resultReg), null)
addInstr(result, IRInstructions.binary(Opcode.MSIGW, IRDataType.LONG, resultReg, tr.resultReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
private fun funcRolRor(call: PtFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val arg = call.args[0]
val vmDt = irType(arg.type)
val vmDt = codeGen.irType(arg.type)
val opcodeMemAndReg = when(call.name) {
"rol" -> Opcode.ROXLM to Opcode.ROXL
"ror" -> Opcode.ROXRM to Opcode.ROXR
@@ -750,39 +786,37 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val ident = arg as? PtIdentifier
if(ident!=null) {
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = ident.name), null)
addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(ident.name)), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
val memAddr: UInt? = (arg as? PtMemoryByte)?.address?.asConstInteger()?.toUInt()
if(memAddr!=null) {
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, address = memAddr.toAddress()), null)
addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(memAddr.toAddress())), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
val arr = (arg as? PtArrayIndexer)
val index = arr?.index?.asConstInteger()
if(arr!=null && index!=null) {
if(arr.variable==null)
TODO("support for ptr indexing ${arr.position}")
if(arr!=null && index!=null && arr.variable!=null) {
val variable = arr.variable!!.name
if(arr.splitWords) {
result += IRCodeChunk(null, null).also {
when(opcodeMemAndReg.first) {
Opcode.ROXRM, Opcode.RORM -> {
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index)
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index)
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_msb", index))
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_lsb", index))
}
Opcode.ROXLM, Opcode.ROLM -> {
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_lsb", symbolOffset = index)
it += IRInstruction(opcodeMemAndReg.first, IRDataType.BYTE, labelSymbol = "${variable}_msb", symbolOffset = index)
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_lsb", index))
it += IRInstructions.memoryOp(opcodeMemAndReg.first, IRDataType.BYTE, IRMemory.direct("${variable}_msb", index))
}
else -> throw AssemblyError("wrong rol/ror opcode")
}
}
} else {
val offset = codeGen.program.memsizer.memorySize(arr.type, index)
addInstr(result, IRInstruction(opcodeMemAndReg.first, vmDt, labelSymbol = variable, symbolOffset = offset), null)
addInstr(result, IRInstructions.memoryOp(opcodeMemAndReg.first, vmDt, IRMemory.direct(variable, offset)), null)
}
return ExpressionCodeResult(result, vmDt, -1, -1)
}
@@ -790,17 +824,17 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val opcode = opcodeMemAndReg.second
val saveCarry = opcode in OpcodesThatDependOnCarry && !arg.isSimple()
if(saveCarry)
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
addInstr(result, IRInstructions.simple(Opcode.PUSHST), null) // save Carry
val tr = exprGen.translateExpression(arg)
addToResult(result, tr, tr.resultReg, -1)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.POPST), null)
addInstr(result, IRInstruction(opcode, vmDt, reg1 = tr.resultReg), null)
addInstr(result, IRInstructions.simple(Opcode.POPST), null)
addInstr(result, IRInstructions.unary(opcode, vmDt, tr.resultReg), null)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.PUSHST), null) // save Carry
addInstr(result, IRInstructions.simple(Opcode.PUSHST), null) // save Carry
result += codeGen.assignRegisterTo(arg, tr.resultReg)
if(saveCarry)
addInstr(result, IRInstruction(Opcode.POPST), null)
addInstr(result, IRInstructions.simple(Opcode.POPST), null)
return ExpressionCodeResult(result, vmDt, -1, -1)
}
@@ -808,60 +842,92 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val target = call.args[0]
val isConstZeroValue = call.args[1].asConstInteger()==0
val isBigEndian = codeGen.options.compTarget.cpu.isBigEndian
fun byteOffset(elementSize: Int) = if(msb != isBigEndian) elementSize - 1 else 0
when(target) {
is PtIdentifier -> {
val offset = if (msb) { if(target.type.isLong) 3 else 1 } else 0
val offset = byteOffset(if(target.type.isLong) 4 else 2)
if(isConstZeroValue) {
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerReg, immediate = offset)
val pointerReg = codeGen.registers.next(IRDataType.POINTER)
it += IRInstructions.loadAddress(IRDataType.POINTER, pointerReg, target.name)
it += IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(pointerReg, offset))
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
val pointerReg = codeGen.registers.next(IRDataType.WORD)
it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = pointerReg, labelSymbol = target.name)
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerReg, immediate = offset)
val pointerReg = codeGen.registers.next(IRDataType.POINTER)
it += IRInstructions.loadAddress(IRDataType.POINTER, pointerReg, target.name)
it += IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, valueTr.resultReg, IRMemory.indirect(pointerReg, offset))
}
}
}
is PtArrayIndexer -> {
if(target.splitWords) {
if(target.variable==null) {
// pointer-based array indexing: compute memory address and store byte
val eltSize = codeGen.program.memsizer.memorySize(target.type, null)
val pointerTr = exprGen.translateExpression(target.pointerderef!!)
addToResult(result, pointerTr, pointerTr.resultReg, -1)
val constIndex = target.index.asConstInteger()
val elementByteOffset = byteOffset(eltSize)
if(constIndex != null) {
val offset = eltSize * constIndex + elementByteOffset
if(offset > 0)
addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerTr.resultReg, offset), null)
} else {
val (code, indexWordReg) = codeGen.loadIndexReg(target.index, eltSize, true, false)
result += code
if(eltSize!=1)
result += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize)
addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerTr.resultReg, indexWordReg), null)
if(elementByteOffset > 0)
addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerTr.resultReg, elementByteOffset), null)
}
if(isConstZeroValue) {
addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, IRDataType.BYTE, IRMemory.indirect(pointerTr.resultReg)), null)
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
addInstr(result, IRInstructions.storeMemory(Opcode.STOREI, IRDataType.BYTE, valueTr.resultReg, IRMemory.indirect(pointerTr.resultReg)), null)
}
}
else if(target.splitWords) {
// lsb/msb in split arrays, element index 'size' is always 1
val constIndex = target.index.asConstInteger()
if(target.variable==null)
TODO("support for ptr indexing ${target.position}")
val varName = target.variable!!.name + if(msb) "_msb" else "_lsb"
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
val idxType = codeGen.options.compTarget.indexRegType
val offsetReg = codeGen.registers.next(idxType)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
it += IRInstructions.load(idxType, offsetReg, constIndex)
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(varName, offsetReg, codeGen.options.compTarget.indexRegType))
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexReg = codeGen.canonicalizeIndexReg(result, indexTr)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = varName)
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(varName, indexReg, codeGen.options.compTarget.indexRegType))
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
val idxType = codeGen.options.compTarget.indexRegType
val offsetReg = codeGen.registers.next(idxType)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
it += IRInstructions.load(idxType, offsetReg, constIndex)
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(varName, offsetReg, codeGen.options.compTarget.indexRegType))
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexReg = codeGen.canonicalizeIndexReg(result, indexTr)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = varName)
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(varName, indexReg, codeGen.options.compTarget.indexRegType))
}
}
}
@@ -873,50 +939,36 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val constIndex = target.index.asConstInteger()
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
val offset = eltSize*constIndex + if(msb) eltSize-1 else 0
val offsetReg = codeGen.registers.next(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE)
val offset = eltSize*constIndex + byteOffset(eltSize)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = targetVariable.name)
it += IRInstructions.load(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, offsetReg, offset)
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(targetVariable.name, offsetReg, codeGen.options.compTarget.indexRegType))
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false)
result += code
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb) {
it += if(target.type.isLong)
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3)
else
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg)
}
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = targetVariable.name)
val offset = byteOffset(eltSize)
it += IRInstructions.storeZero(Opcode.STOREZX, IRDataType.BYTE, IRMemory.indexed(targetVariable.name, indexReg, codeGen.options.compTarget.indexRegType, scale = eltSize, displacement = offset))
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
val offset = eltSize*constIndex + if(msb) eltSize-1 else 0
val offsetReg = codeGen.registers.next(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE)
val offset = eltSize*constIndex + byteOffset(eltSize)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = offset)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = targetVariable.name)
it += IRInstructions.load(if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE, offsetReg, offset)
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(targetVariable.name, offsetReg, codeGen.options.compTarget.indexRegType))
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false)
result += code
result += IRCodeChunk(null, null).also {
if(eltSize>1)
it += codeGen.multiplyByConst(DataType.UBYTE, indexTr.resultReg, eltSize)
if(msb) {
it += if(target.type.isLong)
IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1 = indexTr.resultReg, immediate = 3)
else
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1 = indexTr.resultReg)
}
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = targetVariable.name)
val offset = byteOffset(eltSize)
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.BYTE, valueTr.resultReg, IRMemory.indexed(targetVariable.name, indexReg, codeGen.options.compTarget.indexRegType, scale = eltSize, displacement = offset))
}
}
}
@@ -926,4 +978,31 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
}
private fun tryFoldStructArrayPoke(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult? {
// Detect pokew(&arr[idx]+fieldOff, value) where arr is struct array with variable idx
// and fold to single STOREX/STOREZX with scale=structSize.
val info = extractStructArrayIndexInfo(call.args[0], codeGen) ?: return null
val isZero = codeGen.isZero(call.args[1])
val result = mutableListOf<IRCodeChunkBase>()
val (idxCode, indexReg) = codeGen.loadIndexReg(info.idxExpr, info.structSize, codeGen.wordArrayIndex, false)
result += idxCode
return if(isZero) {
result += IRCodeChunk(null, null).also {
it += IRInstructions.storeZero(Opcode.STOREZX, dt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
}
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
result += IRCodeChunk(null, null).also {
if(dt==IRDataType.FLOAT) {
it += IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultFpReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
} else {
it += IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
}
}
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,36 @@
package prog8.codegen.intermediate
import prog8.intermediate.*
/*
Construction helpers for the few IR opcodes that have no dedicated IRInstructions factory,
plus a small operand-inspection helper used by the optimizers.
The structured IRInstructions factory covers almost every opcode, but three opcodes have an
operand shape that no named factory produces (a lone source register, or a source register plus
an immediate without a destination). They are built here from schema-derived operands, so this
module never constructs IRInstruction directly.
*/
/** STOREHFACZERO / STOREHFACONE: a single source float register (no dedicated factory exists). */
internal fun storeHwFac(opcode: Opcode, fpRegister: Int): IRInstruction =
IRInstructions.push(IRDataType.FLOAT, fpRegister).copy(opcode = opcode)
/** BITTST: a source register plus a byte immediate bit number, with no destination register. */
internal fun bitTest(type: IRDataType, register: Int, bit: Int): IRInstruction =
IRInstructions.simple(Opcode.NOP).copy(
opcode = Opcode.BITTST,
type = type,
srcA = IRInstructions.operandFor(Opcode.BITTST, type, InstructionSlot.SRC_A, register),
immediate = IRInstructions.immediateFor(Opcode.BITTST, type, bit)
)
/**
* The name of the single symbol this instruction refers to, if any.
* That is either the symbol a memory reference is based on, the symbol whose address is taken
* in an immediate operand, or the label a branch/jump/call transfers control to.
*/
internal fun IRInstruction.referencedSymbol(): String? =
memory?.symbolName
?: (immediate as? ImmediateOperand.SymbolAddress)?.symbol
?: labelTarget
File diff suppressed because it is too large. Load diff
@@ -11,9 +11,13 @@ class IRUnusedCodeRemover(
fun optimize(): Int {
var numRemoved = removeUnusedSubroutines() + removeUnusedAsmSubroutines()
// remove empty blocks
// remove empty blocks (but keep blocks with %option force_output,
// and also blocks that contain labels -- labels are addressable symbols,
// and blocks that still have variables in the symbol table -- a block with variables is not truly empty)
irprog.blocks.reversed().forEach { block ->
if(block.isEmpty()) {
val hasLabels = block.children.any { it.label != null }
val hasVariables = irprog.st.allVariables().any { it.name.startsWith(block.label + ".") }
if(!hasLabels && !hasVariables && block.isEmpty() && !block.options.forceOutput) {
irprog.blocks.remove(block)
pruneSymboltable(block.label)
numRemoved++
@@ -33,7 +37,7 @@ class IRUnusedCodeRemover(
blockVars.forEach { stVar ->
irprog.allSubs().flatMap { it.chunks }.forEach { chunk ->
chunk.instructions.forEach { ins ->
if(ins.labelSymbol == stVar.name) {
if(ins.referencedSymbol() == stVar.name) {
return // symbol occurs in an instruction
}
}
@@ -70,27 +74,26 @@ class IRUnusedCodeRemover(
private fun removeBlockInits(code: IRProgram, blockLabel: String) {
val instructions = code.globalInits.instructions
instructions.toTypedArray().forEach {ins ->
if(ins.labelSymbol?.startsWith(blockLabel)==true) {
if(ins.referencedSymbol()?.startsWith(blockLabel)==true) {
instructions.remove(ins)
}
}
// remove stray loads
val readRegs = mutableSetOf<VirtualRegister>()
instructions.forEach { ins ->
val readRegsCounts = mutableMapOf<VirtualRegister, Int>()
val writeRegsCounts = mutableMapOf<VirtualRegister, Int>()
val regsTypes = mutableMapOf<VirtualRegister, IRDataType>()
ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, regsTypes, null)
readRegs.addAll(readRegsCounts.keys)
}
instructions.toTypedArray().forEach { ins ->
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
if(ins.reg1!=0) {
if(instructions.count { it.reg1==ins.reg1 || it.reg2==ins.reg1 } <2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
}
else if(ins.fpReg1!=null) {
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
if(ins.labelSymbol!=null)
code.st.removeIfExists(ins.labelSymbol!!)
instructions.remove(ins)
}
val dest = ins.dest
if(dest != null && dest.register !in readRegs) {
ins.referencedSymbol()?.let { code.st.removeIfExists(it) }
instructions.remove(ins)
}
}
}
@@ -173,8 +176,8 @@ class IRUnusedCodeRemover(
// check if asmsub is linked or called from another regular subroutine
irprog.foreachCodeChunk { chunk ->
chunk.instructions.forEach {
it.labelSymbol?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } }
// note: branchTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set
it.referencedSymbol()?.let { label -> allSubs[label]?.let { cc -> linkedAsmSubs += cc } }
// note: codeTarget can't yet point to another IRAsmSubroutine, so do nothing when it's set
}
}
@@ -193,10 +196,15 @@ class IRUnusedCodeRemover(
}
return numRemoved
}
private val entrypointNames = setOf("p8b_main.p8s_start", "main.start")
private val mainBlockNames = setOf("p8b_main", "main")
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
val entrypointSub = irprog.blocks.single { it.label=="main" }
.children.single { it is IRSubroutine && it.label=="main.start" }
val entrypointSub = irprog.blocks.single { it.label in mainBlockNames }
.children.single { it is IRSubroutine && it.label in entrypointNames }
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
reachable.add(irprog.globalInits)
@@ -217,17 +225,26 @@ class IRUnusedCodeRemover(
val new = mutableSetOf<IRCodeChunkBase>()
reachable.forEach {
it.next?.let { next -> new += next }
if (it is IRLoopChunk) {
// A reachable counted loop makes every body chunk reachable too.
// The body is no longer represented by next pointers in the outer list.
new += it.body
}
it.instructions.forEach { instr ->
if (instr.branchTarget == null)
instr.labelSymbol?.let { label ->
val codeTarget = instr.codeTarget
if (codeTarget == null) {
instr.referencedSymbol()?.let { label ->
val chunk = allLabeledChunks[label] ?: allLabeledChunks[label.substringBeforeLast('.')]
if(chunk!=null)
new+=chunk
else
allLabeledChunks[label]?.let { c -> new += c }
}
else
new += instr.branchTarget!!
}
else {
val chunk = irprog.resolveCodeTarget(codeTarget)
?: codeTarget.labelName?.let { label -> allLabeledChunks[label] }
if(chunk!=null)
new += chunk
}
}
}
reachable += new
@@ -263,21 +280,25 @@ class IRUnusedCodeRemover(
irprog.foreachCodeChunk { chunk ->
chunk.next?.let { next -> linkedChunks += next }
chunk.instructions.forEach {
if(it.branchTarget==null) {
it.labelSymbol?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
val codeTarget = it.codeTarget
if(codeTarget==null) {
it.referencedSymbol()?.let { label -> allLabeledChunks[label]?.let { cc -> linkedChunks += cc } }
} else {
linkedChunks += it.branchTarget!!
val target = irprog.resolveCodeTarget(codeTarget)
?: codeTarget.labelName?.let { label -> allLabeledChunks[label] }
if(target!=null)
linkedChunks += target
}
}
if (chunk.label == "main.start")
if (chunk.label in entrypointNames)
linkedChunks += chunk
}
// make sure that chunks that are only used as a prefix of a label, are also marked as linked
linkedChunks.toList().forEach { chunk ->
chunk.instructions.forEach {
if(it.labelSymbol!=null) {
val chunkName = it.labelSymbol!!.substringBeforeLast('.')
it.referencedSymbol()?.let { symbol ->
val chunkName = symbol.substringBeforeLast('.')
allLabeledChunks[chunkName]?.let { c -> linkedChunks += c }
}
}
@@ -290,6 +311,14 @@ class IRUnusedCodeRemover(
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
var numRemoved = 0
irprog.foreachSub { sub ->
// don't remove stuff in library modules or with %forceoutput
// TODO this is needed to keep the subroutine body of sqrt_long from being wiped !??! Sounds like the bad solution to the problem: why is the *body* being cleared , making an empty sqrt_long subrotuine end up in the p8ir output (and eventually in the .asm, which crashes the resulting program)
// TODO this causes other stuff to end up in the output IR that is not used......
// note: this wasn't a problem on the virtual target, but it is a problem when using the newcodegen targets (because on the virtual target, the VM simply executes the SQRT instruction directly without needing a library routine)
val block = irprog.blocks.first { b -> b.children.any { it === sub } }
if (block.library || block.options.forceOutput)
return@foreachSub
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
if (chunk !in linkedChunks) {
if (chunk === sub.chunks[0]) {
@@ -301,6 +330,9 @@ class IRUnusedCodeRemover(
numRemoved++
}
}
is IRLoopChunk -> {
// keep loop at subroutine entry; don't remove
}
is IRInlineAsmChunk, is IRInlineBinaryChunk -> {
sub.chunks[index] = IRCodeChunk(chunk.label, chunk.next)
numRemoved++
@@ -315,4 +347,4 @@ class IRUnusedCodeRemover(
}
return numRemoved
}
}
}
@@ -0,0 +1,466 @@
package prog8.codegen.intermediate
import prog8.intermediate.*
/*
* ============================================================================
* PROTOTYPE - NOT FOR PRODUCTION USE
* ============================================================================
*
* This is a PROTOTYPE memory-slot packer and the intended reusable foundation
* for a future hardware-register allocator targeting the m68k backend. It is
* NOT a complete or production-ready register allocator in its current form.
* It is DISABLED in production: its only call site in IRCodeGen.generate() is
* commented out, and it currently runs only under TestRegisterPacker.
*
* What this prototype demonstrates:
* - A working graph-coloring register allocator skeleton: CFG construction,
* intraprocedural liveness analysis (gen/kill fixed-point), live interval
* derivation, conflict-graph construction, greedy coloring, and IR rewrite.
* - Coalescing of virtual registers into shared memory slots in the flat
* `p8_regfile` BSS block (vregs with non-overlapping live ranges share a
* slot).
*
* What this prototype DOES NOT do (and why it is not production-ready):
* - No hardware-register allocation: slots are memory addresses in a flat
* BSS regfile, not physical D/A/FP registers.
* - No CALL clobbering: a callee packed into the same slot as a caller's
* live value silently overwrites it (see bug 3 below).
* - No register classes: no distinction between DATA, ADDRESS, and FPU
* registers (required by the m68k backend).
* - No spilling, prologue/epilogue emission, or CALL-aware interference.
* - Known-unsound liveness for complex control flow (nested loops,
* conditionals, early returns) - see bug 2 below.
*
* Intended reuse path (per ideas/m68k-register-allocation.md):
* - Stage 1: Make the m68k backend's instruction selection location-agnostic
* (operand() indirection returning "d3" if allocated, "p8_regfile+N" if
* spilled) - prerequisite bulk work, no behavior change.
* - Stage 2: Replace this packer's interference model with a correct one
* (CALL-aware, class-constrained) and point the coloring step at real
* D2-D6 / A2-A4 / FP2-FP7 registers. Six open design decisions remain
* in Stage 2 (see design doc section 7.1).
* - Stage 3: Delete subsumed peephole optimizations, update test assertions.
*/
/*
* Known bugs / unsoundness (carried over from the removed register-packing.md):
*
* 1. Disjoint-interval value clobbering (observed on cx16 Fibonacci).
* A register with multiple DISJOINT live ranges (e.g. a loop counter written
* at loop entry and read at loop exit, with no uses in between) was split
* into separate intervals; the packer allowed another register to share the
* same slot during the gap, overwriting the value before its next read.
* Fix applied (see packSubroutine interval merging): always merge ALL
* intervals of the same register into one contiguous range, regardless of
* overlap, so the value persists across gaps.
*
* 2. Complex control flow (observed on m68k TextElite).
* The iterative gen/kill fixed-point liveness (computeLiveness) produced
* incorrect live ranges for subroutines with complex CFGs (nested
* conditionals, loops, early returns). Two registers with genuinely
* overlapping live ranges were assigned the same slot, so one clobbered the
* other at runtime (symptom: infinite loop printing spaces — galaxy map
* data read from the wrong memory location). Root cause: the dataflow does
* not converge to the true meet-over-all-paths solution for all loop /
* conditional structures. The liveness MUST be validated against nested
* loops, conditionals, early returns, and switch-like dispatch before reuse.
*
* 3. Cross-subroutine slot collision (fundamental design issue; see header
* comment above). Per-subroutine packing into ONE flat shared regfile is
* unsound: a callee packed into the same slot as a caller's live value
* clobbers it. The packer does not model CALL clobbering. Soundness would
* require the full-program call tree (call-graph-aware allocation, depth
* ranges, or save/restore around calls) — which is exactly what the m68k
* allocator with a calling convention avoids by allocating per-subroutine.
*
* 4. Interval-merge type loss / silent skips. When merging a register's
* intervals, the merged interval keeps only the FIRST interval's type; if a
* later interval has an incompatible type, the register is dropped via
* skipRegs (and its original number is preserved via the startSlot =
* maxReg + 1 invariant). When reusing this code, merged intervals must carry
* the WIDEST type, not just the first, to avoid under-sized slots.
*/
object RegisterPacker {
data class Interval(val register: VirtualRegister, val start: Int, val end: Int, val type: IRDataType)
fun pack(irProg: IRProgram) {
val allRegTypes = mutableMapOf<VirtualRegister, IRDataType>()
irProg.forEachInstruction { instr ->
for (access in instr.registerAccesses)
allRegTypes.putIfAbsent(access.register, access.type)
}
val beforeCount = allRegTypes.size
// Start packing slots after the highest original register number to avoid
// collisions between packed slot numbers and original (non-packed) register numbers.
val maxReg = allRegTypes.keys.maxOfOrNull { it.num } ?: 0
val startSlot = maxReg + 1
val globalSlotTypes = mutableMapOf<VirtualRegister, IRDataType>()
irProg.foreachSub { sub -> packSubroutine(irProg, sub, allRegTypes, globalSlotTypes, startSlot) }
val afterTypes = rebuildTypeMap(irProg)
val afterCount = afterTypes.size
if (!irProg.options.quiet)
println("Register packing: $beforeCount -> $afterCount registers")
}
// Rebuild the register type map after packing, using the same type determination as the packer.
// This avoids the strict per-instruction validation in usedRegisters() that POINTER↔LONG↔WORD
// cross-type packing can trigger.
fun rebuildTypeMap(irProg: IRProgram): Map<VirtualRegister, IRDataType> {
val newTypes = mutableMapOf<VirtualRegister, IRDataType>()
irProg.forEachInstruction { instr ->
for (access in instr.registerAccesses)
newTypes.putIfAbsent(access.register, access.type)
}
return newTypes
}
private fun packSubroutine(irProg: IRProgram, sub: IRSubroutine, allRegTypes: Map<VirtualRegister, IRDataType>, globalSlotTypes: MutableMap<VirtualRegister, IRDataType>, startSlot: Int) {
if (sub.chunks.isEmpty())
return
val successors = buildCFG(sub, irProg)
val (_, liveOut) = computeLiveness(sub, successors)
val codeChunks = sub.chunks.filterIsInstance<IRCodeChunk>()
if (codeChunks.isEmpty())
return
val chunkRanges = mutableMapOf<IRCodeChunk, IntRange>()
var globalIdx = 0
for (chunk in codeChunks) {
val start = globalIdx
val end = globalIdx + chunk.instructions.size - 1
chunkRanges[chunk] = start..end
globalIdx += chunk.instructions.size
}
val registerIntervals = mutableMapOf<VirtualRegister, MutableList<Interval>>()
val registerTypes = mutableMapOf<VirtualRegister, IRDataType>()
for (chunk in codeChunks) {
val range = chunkRanges[chunk]!!
val chunkStart = range.first
if (chunk.instructions.isEmpty())
continue
val liveSet = liveOut[chunk]?.toMutableSet() ?: mutableSetOf()
val lastUse = mutableMapOf<VirtualRegister, Int>()
// Scan backward through instructions
for (i in chunk.instructions.indices.reversed()) {
val instr = chunk.instructions[i]
val globalI = chunkStart + i
val accesses = instr.registerAccesses
// Process READ (USE / USE_DEF) first so types are set before WRITE processing
for (access in accesses) {
if (access.direction != OperandDirection.DEF) {
val r = access.register
registerTypes.putIfAbsent(r, access.type)
if (r !in liveSet) {
lastUse[r] = globalI
liveSet.add(r)
}
}
}
// Process WRITTEN (DEF / USE_DEF) registers: end their live range (start of interval going forward)
for (access in accesses) {
if (access.direction != OperandDirection.USE) {
val r = access.register
if (r in liveSet) {
val end = lastUse.getOrElse(r) { globalI }
registerIntervals.getOrPut(r) { mutableListOf() }
.add(Interval(r, globalI, end, access.type))
liveSet.remove(r)
lastUse.remove(r)
}
}
}
}
// Registers still live at start of chunk are live-in
for (r in liveSet) {
val end = lastUse.getOrElse(r) { chunkStart }
registerIntervals.getOrPut(r) { mutableListOf() }
.add(Interval(r, chunkStart, end, registerTypes.getOrElse(r) { IRDataType.BYTE }))
}
}
if (registerIntervals.isEmpty())
return
// Merge adjacent/overlapping intervals for each register.
// Also merge non-overlapping intervals of the same register into a single
// contiguous range, because the value in the slot must persist across the
// gap to the next use (otherwise another register packed to the same slot
// would clobber it between intervals).
val mergedIntervals = mutableListOf<Interval>()
val skipRegs = mutableSetOf<VirtualRegister>()
for ((reg, intervals) in registerIntervals) {
val sorted = intervals.sortedBy { it.start }
var current = sorted.first()
for (next in sorted.drop(1)) {
if (current.type != next.type && !typesCompatible(current.type, next.type)) {
skipRegs.add(reg)
}
// Always merge: same register's intervals are merged into a single
// contiguous range to preserve the value across gaps.
current = Interval(reg, current.start, maxOf(current.end, next.end), current.type)
}
mergedIntervals.add(current)
}
if (skipRegs.isNotEmpty()) {
mergedIntervals.removeAll { it.register in skipRegs }
}
val conflictGraph = buildConflictGraph(sub)
val packing = greedyColor(mergedIntervals, conflictGraph, allRegTypes, globalSlotTypes, startSlot)
if (packing.isNotEmpty())
rewrite(sub, packing)
}
private fun buildConflictGraph(sub: IRSubroutine): Map<VirtualRegister, Set<VirtualRegister>> {
val conflicts = mutableMapOf<VirtualRegister, MutableSet<VirtualRegister>>()
for (chunk in sub.chunks.filterIsInstance<IRCodeChunk>()) {
val typeRegs = mutableMapOf<IRDataType, MutableSet<VirtualRegister>>()
for (instr in chunk.instructions) {
val accesses = instr.registerAccesses
for (access in accesses)
typeRegs.getOrPut(access.type) { mutableSetOf() }.add(access.register)
// Registers accessed together within a single instruction must not share a slot
// (generalizes the old reg1/reg2-only conflict rule to every register access of
// the instruction, now that they are all enumerable via registerAccesses).
val regsInInstr = accesses.map { it.register }.distinct()
for (i in regsInInstr.indices) {
for (j in i + 1 until regsInInstr.size) {
val r1 = regsInInstr[i]
val r2 = regsInInstr[j]
conflicts.getOrPut(r1) { mutableSetOf() }.add(r2)
conflicts.getOrPut(r2) { mutableSetOf() }.add(r1)
}
}
}
// Add conflicts between registers with incompatible types in the same chunk
val typeList = typeRegs.entries.toList()
for (i in typeList.indices) {
for (j in i + 1 until typeList.size) {
val (t1, regs1) = typeList[i]
val (t2, regs2) = typeList[j]
if (t1 != t2 && !typesCompatible(t1, t2)) {
for (r1 in regs1) {
for (r2 in regs2) {
if (r1 != r2) {
conflicts.getOrPut(r1) { mutableSetOf() }.add(r2)
conflicts.getOrPut(r2) { mutableSetOf() }.add(r1)
}
}
}
}
}
}
}
return conflicts
}
private fun buildCFG(sub: IRSubroutine, irProg: IRProgram): Map<IRCodeChunkBase, List<IRCodeChunkBase>> {
val successors = mutableMapOf<IRCodeChunkBase, MutableList<IRCodeChunkBase>>()
val conditionals = setOf(
Opcode.BSTCC, Opcode.BSTCS, Opcode.BSTEQ, Opcode.BSTNE,
Opcode.BSTNEG, Opcode.BSTPOS, Opcode.BSTVC, Opcode.BSTVS,
Opcode.BGTR, Opcode.BGT, Opcode.BLT,
Opcode.BGTSR, Opcode.BGTS, Opcode.BLTS,
Opcode.BGER, Opcode.BGE, Opcode.BLE,
Opcode.BGESR, Opcode.BGES, Opcode.BLES
)
for (chunk in sub.chunks) {
val succ = mutableListOf<IRCodeChunkBase>()
if (chunk is IRCodeChunk) {
val lastInstr = chunk.instructions.lastOrNull()
val endsWithUnconditional = lastInstr != null && lastInstr.opcode in OpcodesThatBranchUnconditionally
val endsWithConditional = lastInstr != null && lastInstr.opcode in conditionals
if (!endsWithUnconditional) {
chunk.next?.let { succ.add(it) }
}
if (endsWithConditional) {
lastInstr.codeTarget?.let { irProg.resolveCodeTarget(it) }?.let { target ->
if (!succ.contains(target))
succ.add(target)
}
} else if (lastInstr?.opcode == Opcode.JUMP) {
succ.clear()
lastInstr.codeTarget?.let { irProg.resolveCodeTarget(it) }?.let { succ.add(it) }
}
} else {
chunk.next?.let { succ.add(it) }
}
successors[chunk] = succ
}
return successors
}
private fun computeLiveness(
sub: IRSubroutine,
successors: Map<IRCodeChunkBase, List<IRCodeChunkBase>>
): Pair<Map<IRCodeChunk, Set<VirtualRegister>>, Map<IRCodeChunk, Set<VirtualRegister>>> {
val liveIn = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
val liveOut = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
val gen = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
val kill = mutableMapOf<IRCodeChunk, MutableSet<VirtualRegister>>()
for (chunk in sub.chunks) {
if (chunk !is IRCodeChunk)
continue
val genSet = mutableSetOf<VirtualRegister>()
val killSet = mutableSetOf<VirtualRegister>()
for (instr in chunk.instructions) {
val written = instr.definitions
val read = instr.uses
for (r in read) {
if (r !in killSet)
genSet.add(r)
}
for (r in written) {
killSet.add(r)
}
}
gen[chunk] = genSet
kill[chunk] = killSet
liveIn[chunk] = mutableSetOf()
liveOut[chunk] = mutableSetOf()
}
var changed = true
while (changed) {
changed = false
for (chunk in sub.chunks.reversed()) {
if (chunk !is IRCodeChunk)
continue
val newLiveOut = mutableSetOf<VirtualRegister>()
for (succ in successors[chunk].orEmpty()) {
if (succ is IRCodeChunk)
newLiveOut.addAll(liveIn[succ].orEmpty())
}
if (newLiveOut != liveOut[chunk]) {
liveOut[chunk] = newLiveOut
changed = true
}
val newLiveIn = mutableSetOf<VirtualRegister>()
newLiveIn.addAll(gen[chunk].orEmpty())
newLiveIn.addAll(liveOut[chunk].orEmpty() - kill[chunk].orEmpty())
if (newLiveIn != liveIn[chunk]) {
liveIn[chunk] = newLiveIn
changed = true
}
}
}
return liveIn to liveOut
}
private fun greedyColor(
intervals: List<Interval>,
conflictGraph: Map<VirtualRegister, Set<VirtualRegister>>,
allRegTypes: Map<VirtualRegister, IRDataType>,
slotTypes: MutableMap<VirtualRegister, IRDataType>,
startSlot: Int
): Map<VirtualRegister, VirtualRegister> {
if (intervals.isEmpty())
return emptyMap()
val packing = mutableMapOf<VirtualRegister, VirtualRegister>()
// Reserve slots for non-packed registers
val packedRegs = intervals.map { it.register }.toSet()
for ((reg, type) in allRegTypes) {
if (reg !in packedRegs)
slotTypes.putIfAbsent(reg, type)
}
val sorted = intervals.sortedWith(compareBy<Interval> { it.type.ordinal }.thenBy { it.start })
val activeSlots = mutableMapOf<VirtualRegister, Pair<VirtualRegister, Int>>()
for (interval in sorted) {
activeSlots.entries.removeAll { it.value.second < interval.start }
var slotNum = startSlot
var slot: VirtualRegister
while (true) {
// a packed slot stays in the same register file (int/float) as the register it replaces
slot = if (interval.register is VirtualRegister.FloatReg) VirtualRegister.float(slotNum) else VirtualRegister.int(slotNum)
if (slot !in activeSlots) {
val existingType = slotTypes[slot]
if (existingType == null || existingType == interval.type || typesCompatible(existingType, interval.type)) {
if (!conflictsWithRegister(slot, interval.register, conflictGraph, packing))
break
}
}
slotNum++
}
packing[interval.register] = slot
slotTypes.putIfAbsent(slot, interval.type) // keep the first-assigned type to prevent type narrowing via typesCompatible chain
activeSlots[slot] = Pair(interval.register, interval.end)
}
return packing
}
private fun typesCompatible(t1: IRDataType, t2: IRDataType): Boolean {
if (t1 == t2) return true
return (t1 == IRDataType.POINTER && t2 in setOf(IRDataType.WORD, IRDataType.LONG)) ||
(t2 == IRDataType.POINTER && t1 in setOf(IRDataType.WORD, IRDataType.LONG))
}
private fun conflictsWithRegister(
slot: VirtualRegister,
register: VirtualRegister,
conflictGraph: Map<VirtualRegister, Set<VirtualRegister>>,
packing: Map<VirtualRegister, VirtualRegister>
): Boolean {
val conflictingRegs = conflictGraph[register] ?: return false
for (cr in conflictingRegs) {
val crSlot = packing[cr]
if (crSlot != null && crSlot == slot)
return true
if (cr !in packing && cr == slot)
return true
}
return false
}
private fun rewrite(sub: IRSubroutine, packing: Map<VirtualRegister, VirtualRegister>) {
for (chunk in sub.chunks) {
if (chunk !is IRCodeChunk)
continue
for (i in chunk.instructions.indices) {
val instr = chunk.instructions[i]
val newInstr = instr.mapRegisters { vr -> packing[vr] ?: vr }
if (newInstr != instr)
chunk.instructions[i] = newInstr
}
}
}
}
@@ -14,6 +14,11 @@ internal class RegisterPool {
private val registerTypes: MutableMap<RegisterNum, IRDataType> = mutableMapOf()
fun getTypes(): Map<RegisterNum, IRDataType> = registerTypes
fun resetTypes(types: Map<RegisterNum, IRDataType>) {
// called after register packing
registerTypes.clear()
registerTypes.putAll(types)
}
init {
for(i in 99000..99099)
@@ -1,16 +1,19 @@
package prog8.codegen.intermediate
import prog8.code.*
import prog8.code.core.DataType
import prog8.code.ast.PtStructField
import prog8.intermediate.*
/**
* Converter object for transforming SymbolTable nodes to IR symbol table nodes.
* Groups all conversion functions together for better organization.
*/
private object StToIrConverter {
private class StToIrConverter(val romable: Boolean) {
fun convert(struct: StStruct): IRStStructDef =
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
IRStStructDef(struct.scopedNameString, makeIrStructFieldsFrom(struct.fields), struct.size)
private fun makeIrStructFieldsFrom(fields: List<PtStructField>): List<IRStStructField> =
fields.map { IRStStructField(it.type, it.name, it.arraySize) }
private fun convertArrayElt(elt: StArrayElement): IRStSymbolicReference {
return when(elt) {
@@ -72,16 +75,21 @@ private object StToIrConverter {
}
val name = if('.' in variable.name) variable.name else variable.scopedNameString
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty)
val hasInit = initValue != null
val inBss = !hasInit || (romable && hasInit)
val readonly = romable && hasInit
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty, inBss, readonly)
}
fun convert(variable: StMemVar): IRStMemVar {
val readonly = romable && variable.address > 255u
if('.' in variable.name) {
return IRStMemVar(
variable.name,
variable.dt,
variable.address,
variable.length
variable.length,
readonly
)
} else {
val scopedName = try {
@@ -89,7 +97,7 @@ private object StToIrConverter {
} catch (_: UninitializedPropertyAccessException) {
variable.name
}
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length, readonly)
}
}
@@ -103,13 +111,14 @@ private object StToIrConverter {
constant.name
}
}
val noPrefix = constant.astNode?.definingBlock()?.options?.noSymbolPrefixing == true
if(constant.value != null)
return IRStConstant(scopedName, constant.dt, constant.value)
return IRStConstant(scopedName, constant.dt, constant.value, noPrefix = noPrefix)
else if(constant.memorySlab != null) {
// For memory() constants, store reference to slab name
// The actual address will be resolved during code generation
val slabName = constant.memorySlab!!.name
return IRStConstant(scopedName, constant.dt, null, slabName)
return IRStConstant(scopedName, constant.dt, null, slabName, noPrefix)
}
else
TODO("constant without value or memory slab: $constant")
@@ -122,10 +131,17 @@ private object StToIrConverter {
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
}
fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType, String>>): IRStStructInstance {
val values = fields.zip(instance.initialValues).map { (field, value) ->
fun convert(instance: StStructInstance, fields: Iterable<PtStructField>): IRStStructInstance {
val expanded = fields.flatMap { field ->
val arraySz = field.arraySize
if(arraySz!=null)
List(arraySz) { field.type.sub!! }
else
listOf(field.type.base)
}
val values = expanded.zip(instance.initialValues).map { (baseType, value) ->
val elt = convertArrayElt(value)
IRStructInitValue(field.first.base, elt)
IRStructInitValue(baseType, elt)
}
return if('.' in instance.name)
IRStStructInstance(instance.name, instance.structName, values, instance.size)
@@ -134,33 +150,34 @@ private object StToIrConverter {
}
}
fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
fun convertStToIRSt(sourceSt: SymbolTable?, romable: Boolean = false): IRSymbolTable {
val st = IRSymbolTable()
val converter = StToIrConverter(romable)
if (sourceSt != null) {
sourceSt.flat.forEach {
when(it.value.type) {
StNodeType.STATICVAR -> st.add(StToIrConverter.convert(it.value as StStaticVariable))
StNodeType.MEMVAR -> st.add(StToIrConverter.convert(it.value as StMemVar))
StNodeType.STATICVAR -> st.add(converter.convert(it.value as StStaticVariable))
StNodeType.MEMVAR -> st.add(converter.convert(it.value as StMemVar))
StNodeType.CONSTANT -> {
val constant = it.value as StConstant
// If the constant has a memory() slab, add the slab to the IR symbol table first
constant.memorySlab?.let { slab ->
val converted = StToIrConverter.convert(slab)
val converted = converter.convert(slab)
st.add(converted)
}
st.add(StToIrConverter.convert(constant))
st.add(converter.convert(constant))
}
StNodeType.MEMORYSLAB -> {
val slab = it.value as StMemorySlab
val converted = StToIrConverter.convert(slab)
val converted = converter.convert(slab)
st.add(converted)
}
StNodeType.STRUCTINSTANCE -> {
val instance = it.value as StStructInstance
val struct = sourceSt.lookup(instance.structName) as StStruct
st.add(StToIrConverter.convert(instance, struct.fields))
st.add(converter.convert(instance, struct.fields))
}
StNodeType.STRUCT -> st.add(StToIrConverter.convert(it.value as StStruct))
StNodeType.STRUCT -> st.add(converter.convert(it.value as StStruct))
else -> { }
}
}
@@ -0,0 +1,52 @@
package prog8.codegen.intermediate
import prog8.code.ast.*
internal data class StructArrayIndexInfo(
val arrayName: String,
val idxExpr: PtExpression,
val structSize: Int,
val fieldOffset: Int
)
internal fun extractStructArrayIndexInfo(
addressExpr: PtExpression,
codeGen: IRCodeGen
): StructArrayIndexInfo? {
fun unwrapCast(expr: PtExpression): PtExpression {
var cur = expr
while(cur is PtTypeCast) cur = cur.value
return cur
}
fun asAddressOf(expr: PtExpression): PtAddressOf? {
return unwrapCast(expr) as? PtAddressOf
}
val baseAddrOf: PtAddressOf
val fieldOffset: Int
val unwrapped = unwrapCast(addressExpr)
when {
unwrapped is PtAddressOf -> {
baseAddrOf = unwrapped
fieldOffset = 0
}
unwrapped is PtBinaryExpression && unwrapped.operator == "+" -> {
val leftAddr = asAddressOf(unwrapped.left) ?: return null
val rightNum = (unwrapCast(unwrapped.right) as? PtNumber)?.number?.toInt() ?: return null
baseAddrOf = leftAddr
fieldOffset = rightNum
}
else -> return null
}
if(!baseAddrOf.isFromArrayElement) return null
val arrayIdent = baseAddrOf.identifier ?: return null
val idxExpr = baseAddrOf.arrayIndexExpr ?: return null
if(idxExpr is PtNumber) return null
val arrayType = arrayIdent.type
if(!arrayType.isArray) return null
val elemType = arrayType.elementType()
if(!elemType.isStructInstance) return null
val structSize = codeGen.program.memsizer.memorySize(elemType, null)
if(structSize <= 0 || structSize > 65535) return null
if(fieldOffset < 0 || fieldOffset > 65535) return null
return StructArrayIndexInfo(arrayIdent.name, idxExpr, structSize, fieldOffset)
}
@@ -0,0 +1,306 @@
package prog8.codegen.intermediate
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.CompilationOptions
import prog8.code.core.DataType
import prog8.code.core.ISubType
/**
* Prefixes all symbol names in the given program's AST with type-specific prefixes
* (p8b_ for blocks, p8s_ for subroutines, p8v_ for variables, p8c_ for constants,
* p8l_ for labels, p8t_ for structs).
*
* The AST should have simple (unscoped) names for all named nodes at this point.
* Identifiers and function calls may already have dotted scoped names.
* After prefixing, call [makeAllNodenamesScoped] to add scoping to the prefix names.
*
* Blocks with `noSymbolPrefixing=true` keep their own names intact,
* but their references to symbols in prefixed blocks still get prefixed.
*
* @return A new [SymbolTable] rebuilt from the now-prefixed AST.
*/
fun prefixSymbols(program: PtProgram, options: CompilationOptions, st: SymbolTable): SymbolTable {
val nodesToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
val functionCallsToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
val expressionsToFixSubtype = mutableListOf<PtExpression>()
val variablesToFixSubtype = mutableListOf<IPtVariable>()
fun prefixNamedNode(node: PtNamedNode) {
when(node) {
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
is PtBlock -> node.name = "p8b_${node.name}"
is PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}"
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
is PtStructDecl -> node.name = "p8t_${node.name}"
}
}
fun prefixSymbols(node: PtNode) {
when(node) {
is PtAsmSub -> {
prefixNamedNode(node)
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
val address = node.address
if (address?.varbank != null) {
address.varbank = address.varbank!!.prefixIdentifierOrThis(node, st)
}
}
is PtSub -> prefixNamedNode(node)
is PtFunctionCall -> {
if(node.builtin) {
if(node.name=="prog8_lib_structalloc") {
val struct = node.type.subType!!
if(struct is StStruct) {
node.type.subType = st.lookup(struct.scopedNameString) as StStruct
}
}
} else {
val stNode = st.lookup(node.name)!!
if (stNode.astNode?.definingBlock()?.options?.noSymbolPrefixing != true) {
val index = node.parent.children.indexOf(node)
functionCallsToPrefix += node.parent to index
}
}
}
is PtIdentifier -> {
val pexpr = node.parent as? PtBinaryExpression
if(pexpr?.operator==".") {
if(pexpr.left is PtArrayIndexer) {
val arrayvar = (pexpr.left as PtArrayIndexer).variable!!
if(arrayvar.type.subType!=null) {
return
} else
throw AssemblyError("can only use deref expression on struct type")
} else {
TODO("handle other left operand ${pexpr.left }in dereferencing of struct fields ${node.position}")
}
}
var lookupName = node.name
if(node.type.isSplitWordArray(program.memsizer) && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
lookupName = lookupName.dropLast(4)
}
val stNode = st.lookup(lookupName) ?:
throw AssemblyError("unknown identifier $node")
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
}
is PtBlock -> prefixNamedNode(node)
is PtConstant -> node.name = "p8c_${node.name}"
is PtLabel -> prefixNamedNode(node)
is PtMemMapped -> prefixNamedNode(node)
is PtSubroutineParameter -> prefixNamedNode(node)
is PtVariable -> {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
is PtStructDecl -> prefixNamedNode(node)
else -> { }
}
if(node is IPtVariable) {
if(node.type.subType!=null)
variablesToFixSubtype.add(node)
} else if(node is PtExpression) {
if(node.type.subType!=null)
expressionsToFixSubtype.add(node)
}
node.children.forEach { prefixSymbols(it) }
}
fun maybePrefixFunctionCallsAndIdentifierReferences(node: PtNode) {
if(node is PtFunctionCall && !node.builtin) {
val stNode = st.lookup(node.name)!!
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
functionCallsToPrefix += node.parent to index
}
}
else if (node is PtIdentifier) {
var lookupName = node.name
if(node.type.isSplitWordArray(program.memsizer) && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
lookupName = lookupName.dropLast(4)
}
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
}
}
node.children.forEach { maybePrefixFunctionCallsAndIdentifierReferences(it) }
}
program.allBlocks().forEach { block ->
if (!block.options.noSymbolPrefixing) {
prefixSymbols(block)
} else {
maybePrefixFunctionCallsAndIdentifierReferences(block)
}
}
nodesToPrefix.forEach { (parent, index) ->
when(val node = parent.children[index]) {
is PtIdentifier -> parent.setChild(index, node.prefixIdentifierOrThis(parent, st))
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
is PtVariable -> parent.setChild(index, node.prefixVariableOrThis(parent, st))
else -> throw AssemblyError("weird node to prefix $node")
}
}
// reversed so inner calls (such as arguments to a function call) get processed before the actual function call itself
functionCallsToPrefix.reversed().forEach { (parent, index) ->
val node = parent.children[index]
if(node is PtFunctionCall) {
val prefixedName = PtIdentifier(node.name, DataType.UNDEFINED, node.position).prefixIdentifierOrThis(parent, st)
val prefixedNode = node.withNewName(prefixedName.name)
parent.setChild(index, prefixedNode)
} else {
throw AssemblyError("expected PtFunctionCall")
}
}
val updatedSt = SymbolTableMaker(program, options).make()
fun findSubtypeReplacement(sub: ISubType): StStruct? {
if(updatedSt.lookup(sub.scopedNameString)!=null)
return null
val old = st.lookup(sub.scopedNameString) ?: throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
val prefixed = ArrayDeque<String>()
var node: StNode = old
while(node.type!= StNodeType.GLOBAL) {
val typeChar = typePrefixChar(node.type)
prefixed.addFirst("p8${typeChar}_${node.name}")
node=node.parent
}
return updatedSt.lookup(prefixed.joinToString(".")) as StStruct
}
expressionsToFixSubtype.forEach { node ->
findSubtypeReplacement(node.type.subType!!)?.let {
node.type.subType = it
}
}
variablesToFixSubtype.forEach { node ->
findSubtypeReplacement(node.type.subType!!)?.let {
node.type.subType = it
}
}
return updatedSt
}
internal fun prefixScopedName(name: String, type: Char): String {
if('.' !in name) {
if(name.startsWith(GENERATED_LABEL_PREFIX))
return name
return "p8${type}_$name"
}
val parts = name.split('.')
val firstPrefixed = "p8b_${parts[0]}"
val lastPart = parts.last()
val lastPrefixed = if(lastPart.startsWith(GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
return prefixed.joinToString(".")
}
internal fun typePrefixChar(targetNt: StNodeType): Char {
return when(targetNt) {
StNodeType.BLOCK -> 'b'
StNodeType.SUBROUTINE, StNodeType.EXTSUB -> 's'
StNodeType.LABEL -> 'l'
StNodeType.STATICVAR, StNodeType.MEMVAR -> 'v'
StNodeType.CONSTANT -> 'c'
StNodeType.BUILTINFUNC -> 's'
StNodeType.MEMORYSLAB -> 'v'
StNodeType.STRUCT -> 't'
StNodeType.STRUCTINSTANCE -> 'i'
else -> '?'
}
}
private fun PtVariable.prefixVariableOrThis(parent: PtNode, st: SymbolTable): PtVariable {
name = prefixScopedName(name, 'v')
if(value==null)
return this
val arrayValue = value as? PtArray
return if(arrayValue!=null && arrayValue.children.any { it !is PtNumber && it !is PtBool } ) {
val newValue = PtArray(arrayValue.type, arrayValue.position)
arrayValue.children.forEach { elt ->
when(elt) {
is PtBool -> newValue.add(elt)
is PtNumber -> newValue.add(elt)
is PtConstant -> newValue.add(elt)
is PtAddressOf -> {
if(elt.definingBlock()?.options?.noSymbolPrefixing==true)
newValue.add(elt)
else {
val newAddr = PtAddressOf(elt.type, false, elt.position)
newAddr.add(elt.identifier!!.prefixIdentifierOrThis(newAddr, st))
if (elt.arrayIndexExpr != null)
newAddr.add(elt.arrayIndexExpr!!)
newAddr.parent = arrayValue
newValue.add(newAddr)
}
}
is PtFunctionCall if elt.builtin -> {
if (elt.name != "prog8_lib_structalloc")
throw AssemblyError("weird array value element $elt")
else
newValue.add(elt)
}
else -> throw AssemblyError("weird array value element $elt")
}
}
val result = PtVariable(name, type, isSplitWordArray, zeropage, align, dirty, newValue, arraySize, position)
result.parent = parent
result
}
else this
}
private fun PtIdentifier.prefixIdentifierOrThis(parent: PtNode, st: SymbolTable): PtIdentifier {
val targetNt: StNodeType
val target = st.lookup(name)
if(target?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
return this
if(target==null) {
if(name.endsWith("_lsb") || name.endsWith("_msb")) {
val target2 = st.lookup(name.dropLast(4))
if(target2?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
return this
targetNt = target2!!.type
} else {
targetNt = StNodeType.STATICVAR
}
} else {
targetNt = target.type
}
val prefixType = typePrefixChar(targetNt)
val newName = prefixScopedName(name, prefixType)
val node = PtIdentifier(newName, type, position)
node.parent = parent
return node
}
private fun PtFunctionCall.withNewName(name: String): PtFunctionCall {
val call = PtFunctionCall(name, builtin, hasNoSideEffects, returntypes, position)
call.addAll(children)
call.children.forEach { it.parent = call }
call.parent = parent
return call
}
@@ -1,26 +1,38 @@
package prog8.codegen.vm
import prog8.code.IAssemblyProgram
import prog8.code.ICodeGeneratorBackend
import prog8.code.SymbolTable
import prog8.code.assembly.IAssemblyProgram
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IErrorReporter
import prog8.codegen.intermediate.IRCodeGen
import prog8.intermediate.IRDataType
import prog8.intermediate.IRFileWriter
import prog8.intermediate.IRProgram
import prog8.intermediate.VirtualRegister
import prog8.intermediate.dumpVariables
class VmCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
class VmCodeGen(val retainSSA: Boolean,
private val preassignedCallSiteIds: Map<String, UByte> = emptyMap()
): ICodeGeneratorBackend {
override fun generate(
program: PtProgram,
symbolTable: SymbolTable,
options: CompilationOptions,
errors: IErrorReporter
): IAssemblyProgram {
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA, preassignedCallSiteIds)
val irProgram = irCodeGen.generate()
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
val virtualRegisterTypes = irCodeGen.registerTypes().entries.associate { (num, type) ->
val register: VirtualRegister = if (type == IRDataType.FLOAT) VirtualRegister.float(num.value) else VirtualRegister.int(num.value)
register to type
}
irProgram.verifyRegisterTypes(virtualRegisterTypes)
if (options.dumpVariables)
dumpVariables(irProgram)
return VmAssemblyProgram(irProgram.name, irProgram)
}
Loaded 100 of 672 files, more files were not shown because too many files have changed in this diff. Show more