Improve inline assembler parser
This commit is contained in:
parent
0e59af5122
commit
d493bddf8d
128
README.md
128
README.md
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue