Improve inline assembler parser

This commit is contained in:
drmortalwombat 2021-10-10 20:51:21 +02:00
parent 0e59af5122
commit d493bddf8d
3 changed files with 140 additions and 22 deletions

128
README.md
View File

@ -49,7 +49,6 @@ After four weeks, the compiler has now matured significantly. There are still s
* Simple loop opmtimization * Simple loop opmtimization
* Partial block domination analysis * Partial block domination analysis
* No register use for arguments
* Auto variables placed on fixed stack for known call sequence * Auto variables placed on fixed stack for known call sequence
### Intermediate code generation ### Intermediate code generation
@ -67,7 +66,7 @@ After four weeks, the compiler has now matured significantly. There are still s
The compiler is command line driven, and creates an executable .prg file. The compiler is command line driven, and creates an executable .prg file.
oscar64 {-i=includePath} [-o=output.prg] [-rt=runtime.c] [-e] [-n] [-dSYMBOL[=value]] {source.c} oscar64 {-i=includePath} [-o=output.prg] [-rt=runtime.c] [-e] [-n] [-dSYMBOL[=value]] {source.c}
* -i : additional include paths * -i : additional include paths
* -o : optional output file name * -o : optional output file name
* -rt : alternative runtime library, replaces the crt.c * -rt : alternative runtime library, replaces the crt.c
@ -77,12 +76,103 @@ The compiler is command line driven, and creates an executable .prg file.
A list of source files can be provided. A list of source files can be provided.
## Inline Assembler
Inline assembler can be embedded inside of any functions, regardles of their compilation target of byte code or native.
### Accessing variables in assembler
Access to local variables and parameters is done with zero page registers, global variables are accessed using absolute addressing.
void putchar(char c)
{
__asm {
lda c
bne w1
lda #13
w1:
jsr 0xffd2
}
}
A function return value can be provided in the zero page addresses ACCU (+0..+3).
char getchar(void)
{
__asm {
jsr 0xffcf
sta accu
lda #0
sta accu + 1
}
}
Labels are defined with a colon after the name. Pure assembler functions can be defined outside of the scope of a function and accessed using their name inside of other assembler function. One can e.g. set up an interrupt
### Interrupt routines
The C compiler will not generate good interrupt code, it is simply too greedy with the zero page registers. Interrupt code should therefore be written in assembler.
#include <math.h>
// Next line for interrupt
char npos;
// Interrupt routine
__asm irq
{
lda $d019 // Check if it is raster IRQ
and #$01
beq w1
inc $d020 // Start colored section
inc $d021
ldx #20 // Wait for 2/3 lines
l1: dex
bne l1
dec $d020 // End colored section
dec $d021
lda npos // Setup next interrupt
sta $d012
w1:
asl $d019 // Ack interrupt
jmp $ea31 // System IRQ routine
}
int main(void)
{
__asm { sei } // Disable interrupt
*(void **)0x0314 = irq; // Install interrupt routine
*(char *)0xd01a = 1; // Enable raster interrupt
*(char *)0xd011 &= 0x7f; // Set raster line for IRQ
*(char *)0xd012 = 100;
npos = 100;
__asm { cli } // Re-enable interrupt
// Move the interrupt raster line up/down
float f = 0;
while (true)
{
npos = 130 + (int)(100 * sin(f));
f += 0.1;
}
return 0;
}
## Implementation Details ## Implementation Details
The compiler does a full program compile, the linker step is part of the compilation. It knows all functions during the compilation run and includes only reachable code in the output. Source files are added to the build with the help of a pragma: The compiler does a full program compile, the linker step is part of the compilation. It knows all functions during the compilation run and includes only reachable code in the output. Source files are added to the build with the help of a pragma:
#pragma compile("stdio.c") #pragma compile("stdio.c")
The character map for string and char constants can be changed with a pragma to match a custon character set or PETSCII. The character map for string and char constants can be changed with a pragma to match a custon character set or PETSCII.
@ -91,32 +181,32 @@ The character map for string and char constants can be changed with a pragma to
The byte code interpreter is compiled by the compiler itself and placed in the source file "crt.c". Functions implementing byte codes are marked with a pragma: The byte code interpreter is compiled by the compiler itself and placed in the source file "crt.c". Functions implementing byte codes are marked with a pragma:
#pragma bytecode(BC_CONST_P8, inp_const_p8) #pragma bytecode(BC_CONST_P8, inp_const_p8)
The functions are written in 6502 assembly with the __asm keyword The functions are written in 6502 assembly with the __asm keyword
__asm inp_const_p8 __asm inp_const_p8
{ {
lda (ip), y lda (ip), y
tax tax
iny iny
lda (ip), y lda (ip), y
sta $00, x sta $00, x
lda #0 lda #0
sta $01, x sta $01, x
iny iny
jmp startup.exec jmp startup.exec
} }
The current byte code program counter is (ip),y. The interpreter loop guarantees that y is always <= 128 and can thus be used to index the additional byte code arguments without the need to check the 16 bit pointer. The interpreter loop itself is quite compact and takes 21 cycles (including the final jump of the byte code function itself). Moving it to zero page would reduce this by another two cycles but is most likely not worth the waste of temporary space. The current byte code program counter is (ip),y. The interpreter loop guarantees that y is always <= 128 and can thus be used to index the additional byte code arguments without the need to check the 16 bit pointer. The interpreter loop itself is quite compact and takes 21 cycles (including the final jump of the byte code function itself). Moving it to zero page would reduce this by another two cycles but is most likely not worth the waste of temporary space.
exec: exec:
lda (ip), y lda (ip), y
sta execjmp + 1 sta execjmp + 1
iny iny
bmi incip bmi incip
execjmp: execjmp:
jmp (0x0900) jmp (0x0900)
The intermediate code generator assumes a large number of registers so the zero page is used for this purpose. The allocation is not yet final: The intermediate code generator assumes a large number of registers so the zero page is used for this purpose. The allocation is not yet final:
@ -134,7 +224,7 @@ Routines can be marked to be compiled to 6502 machine code with the native pragm
void Plot(int x, int y) void Plot(int x, int y)
{ {
(*Bitmap)[y >> 3][x >> 3][y & 7] |= 0x80 >> (x & 7); (*Bitmap)[y >> 3][x >> 3][y & 7] |= 0x80 >> (x & 7);
} }
#pragma native(Plot) #pragma native(Plot)

View File

@ -668,6 +668,23 @@ InterCodeGenerator::ExValue InterCodeGenerator::TranslateExpression(Declaration*
} break; } break;
case DT_CONST_ASSEMBLER:
{
if (!dec->mLinkerObject)
TranslateAssembler(proc->mModule, dec->mValue, nullptr);
InterInstruction* ins = new InterInstruction();
ins->mCode = IC_CONSTANT;
ins->mDst.mType = IT_POINTER;
ins->mDst.mTemp = proc->AddTemporary(ins->mDst.mType);
ins->mConst.mVarIndex = dec->mVarIndex;
ins->mConst.mLinkerObject = dec->mLinkerObject;
ins->mConst.mMemory = IM_PROCEDURE;
ins->mConst.mIntConst = 0;
block->Append(ins);
return ExValue(TheVoidPointerTypeDeclaration, ins->mDst.mTemp);
}
case DT_CONST_POINTER: case DT_CONST_POINTER:
{ {
vl = TranslateExpression(procType, proc, block, dec->mValue, breakBlock, continueBlock, inlineMapper); vl = TranslateExpression(procType, proc, block, dec->mValue, breakBlock, continueBlock, inlineMapper);

View File

@ -1991,6 +1991,17 @@ Expression* Parser::ParseAssemblerBaseOperand(void)
else else
mErrors->Error(mScanner->mLocation, EERR_INCOMPATIBLE_OPERATOR, "Identifier for qualification expected"); mErrors->Error(mScanner->mLocation, EERR_INCOMPATIBLE_OPERATOR, "Identifier for qualification expected");
} }
if (exp->mDecValue->mType == DT_CONST_ASSEMBLER)
{
Declaration* ndec = new Declaration(mScanner->mLocation, DT_LABEL);
ndec->mIdent = exp->mDecValue->mIdent;
ndec->mBase = exp->mDecValue;
ndec->mInteger = 0;
exp->mDecValue = ndec;
exp->mDecType = TheUnsignedIntTypeDeclaration;
}
break; break;
default: default:
mErrors->Error(mScanner->mLocation, EERR_ASM_INVALD_OPERAND, "Invalid assembler operand"); mErrors->Error(mScanner->mLocation, EERR_ASM_INVALD_OPERAND, "Invalid assembler operand");
@ -2286,7 +2297,7 @@ Expression* Parser::ParseAssembler(void)
{ {
ilast->mAsmInsType = ins; ilast->mAsmInsType = ins;
mScanner->NextToken(); mScanner->NextToken();
if (mScanner->mToken == TK_EOL) if (mScanner->mToken == TK_EOL || mScanner->mToken == TK_CLOSE_BRACE)
ilast->mAsmInsMode = ASMIM_IMPLIED; ilast->mAsmInsMode = ASMIM_IMPLIED;
else if (mScanner->mToken == TK_HASH) else if (mScanner->mToken == TK_HASH)
{ {
@ -2382,12 +2393,12 @@ Expression* Parser::ParseAssembler(void)
if (ilast->mAsmInsType == ASMIT_BYTE) if (ilast->mAsmInsType == ASMIT_BYTE)
ilast->mAsmInsMode = ASMIM_IMMEDIATE; ilast->mAsmInsMode = ASMIM_IMMEDIATE;
if (mScanner->mToken != TK_EOL) if (mScanner->mToken != TK_EOL && mScanner->mToken != TK_CLOSE_BRACE)
{ {
mErrors->Error(mScanner->mLocation, EERR_SYNTAX, "End of line expected"); mErrors->Error(mScanner->mLocation, EERR_SYNTAX, "End of line expected");
} }
while (mScanner->mToken != TK_EOL && mScanner->mToken != TK_EOF) while (mScanner->mToken != TK_EOL && mScanner->mToken != TK_EOF && mScanner->mToken != TK_CLOSE_BRACE)
mScanner->NextToken(); mScanner->NextToken();
offset += AsmInsSize(ilast->mAsmInsType, ilast->mAsmInsMode); offset += AsmInsSize(ilast->mAsmInsType, ilast->mAsmInsMode);