Last active
December 12, 2022 08:28
-
-
Save russdill/abbacfe5dba1ba4070584efe44f46a58 to your computer and use it in GitHub Desktop.
Example of AVR custom jump tables using inline assembler "asm goto" construct
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef JUMP_TABLE_H | |
#define JUMP_TABLE_H | |
#define MAP_OUT | |
#define EVAL0(...) __VA_ARGS__ | |
#define EVAL1(...) EVAL0 (EVAL0 (EVAL0 (__VA_ARGS__))) | |
#define EVAL2(...) EVAL1 (EVAL1 (EVAL1 (__VA_ARGS__))) | |
#define EVAL3(...) EVAL2 (EVAL2 (EVAL2 (__VA_ARGS__))) | |
#define EVAL4(...) EVAL3 (EVAL3 (EVAL3 (__VA_ARGS__))) | |
#define EVAL(...) EVAL4 (EVAL4 (EVAL4 (__VA_ARGS__))) | |
#define MAP_END(...) | |
#define MAP_GET_END() 0, MAP_END | |
#define MAP_NEXT0(item, next, ...) next MAP_OUT | |
#define MAP_NEXT1(item, next) MAP_NEXT0 (item, next, 0) | |
#define MAP_NEXT(item, next) MAP_NEXT1 (MAP_GET_END item, next) | |
#define MAP0(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP1) (f, peek, __VA_ARGS__) | |
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT (peek, MAP0) (f, peek, __VA_ARGS__) | |
#define MAP(f, ...) EVAL (MAP1 (f, __VA_ARGS__, (), 0)) | |
#define __COMBINE(x, y) x ## y | |
#define __CALL(x, IDX, n...) x(IDX, n) | |
#define JUMP_TABLE1(IDX, IDX_TYPE, n...) __CALL(__COMBINE(JUMP_TABLE1_, IDX_TYPE), IDX, n) | |
#define JUMP_TABLE2(IDX, IDX_TYPE, n...) __CALL(__COMBINE(JUMP_TABLE2_, IDX_TYPE), IDX, n) | |
// 5 ins (10 bytes) + 2 bytes per target | |
#define RJMP_LABEL(x) "\trjmp\t%l[" #x "]\n" | |
/* Note, lo8(-1f)/hi8(-1f) is currently buggy, so subi/sbc can't be used. | |
* workaround is an alternate codepath with an extra instruciton */ | |
#define JUMP_TABLE2_R30(...) \ | |
asm goto( \ | |
" ldi r31, pm_lo8(1f)\n" \ | |
" add r30, r31\n" \ | |
" ldi r31, pm_hi8(1f)\n" \ | |
" adc r31, __zero_reg__\n" \ | |
" ijmp\n" \ | |
"1:\n" \ | |
MAP(RJMP_LABEL, __VA_ARGS__) \ | |
::: "r30", "r31" : __VA_ARGS__) | |
#define JUMP_TABLE2_MEM(IDX, ...) \ | |
asm goto( \ | |
" lds r30, %[idx]\n" \ | |
" ldi r31, pm_lo8(1f)\n" \ | |
" add r30, r31\n" \ | |
" ldi r31, pm_hi8(1f)\n" \ | |
" adc r31, __zero_reg__\n" \ | |
" ijmp\n" \ | |
"1:\n" \ | |
MAP(RJMP_LABEL, __VA_ARGS__) \ | |
:: [idx]"m"(IDX) : "r30", "r31" : __VA_ARGS__) | |
#define JUMP_TABLE2_REG(IDX, ...) \ | |
asm goto( \ | |
" mov r30, %[idx]\n" \ | |
" ldi r31, pm_lo8(1f)\n" \ | |
" add r30, r31\n" \ | |
" ldi r31, pm_hi8(1f)\n" \ | |
" adc r31, __zero_reg__\n" \ | |
" ijmp\n" \ | |
"1:\n" \ | |
MAP(RJMP_LABEL, __VA_ARGS__) \ | |
:: [idx]"r"(IDX) : "r30", "r31" : __VA_ARGS__) | |
// 10 ins (20 bytes) + 1 byte per near target (512 bytes), 3 bytes per far | |
#define BYTE_LABEL(x) "\t.byte (%l[" #x "] - . + 1)/2\n" | |
#if defined (__AVR_TINY__) | |
#define __LPM "ld" | |
#define __BASE "0x4000" | |
#else | |
#define __LPM "lpm" | |
#define __BASE "0x0" | |
#endif | |
/* Note, lo8(-1f)/hi8(-1f) is currently buggy, so subi/sbc can't be used. | |
* workaround is an alternate codepath with an extra instruciton */ | |
#define JUMP_TABLE1_R30(...) \ | |
asm goto( \ | |
" ldi r31, lo8(1f + " __BASE ")\n" \ | |
" add r30, r31\n" \ | |
" ldi r31, hi8(1f + " __BASE ")\n" \ | |
" adc r31, __zero_reg__\n" \ | |
" " __LPM " r16, Z\n" \ | |
" lsr r31\n" \ | |
" ror r30\n" \ | |
" add r30, r16\n" \ | |
" adc r31, __zero_reg__\n" \ | |
" ijmp\n" \ | |
"1:\n" \ | |
MAP(BYTE_LABEL, __VA_ARGS__) \ | |
".align 1\n" \ | |
::: "r16", "r30", "r31" : __VA_ARGS__) | |
#define JUMP_TABLE1_MEM(IDX, ...) \ | |
asm goto( \ | |
" lds r30, %[idx]\n" \ | |
" ldi r31, lo8(1f + " __BASE ")\n" \ | |
" add r30, r31\n" \ | |
" ldi r31, hi8(1f + " __BASE ")\n" \ | |
" adc r31, __zero_reg__\n" \ | |
" " __LPM " r16, Z\n" \ | |
" lsr r31\n" \ | |
" ror r30\n" \ | |
" add r30, r16\n" \ | |
" adc r31, __zero_reg__\n" \ | |
" ijmp\n" \ | |
"1:\n" \ | |
MAP(BYTE_LABEL, __VA_ARGS__) \ | |
".align 1\n" \ | |
:: [idx]"m"(IDX) : "r16", "r30", "r31" : __VA_ARGS__) | |
#define JUMP_TABLE1_REG(IDX, ...) \ | |
asm goto( \ | |
" mov r30, %[idx]\n" \ | |
" ldi r31, lo8(1f + " __BASE ")\n" \ | |
" add r30, r31\n" \ | |
" ldi r31, hi8(1f + " __BASE ")\n" \ | |
" adc r31, __zero_reg__\n" \ | |
" " __LPM " r16, Z\n" \ | |
" lsr r31\n" \ | |
" ror r30\n" \ | |
" add r30, r16\n" \ | |
" adc r31, __zero_reg__\n" \ | |
" ijmp\n" \ | |
"1:\n" \ | |
MAP(BYTE_LABEL, __VA_ARGS__) \ | |
".align 1\n" \ | |
:: [idx]"m"(IDX) : "r16", "r30", "r31" : __VA_ARGS__) | |
// 11 ins (22 bytes) + 2 bytes per target (gcc) | |
#if 0 | |
" lds r20, lo8(idx)\n" | |
" ldi r21, high8(idx)\n" | |
" cpi r20, 0x0B\n" | |
" cpc r21, r17\n" | |
" brcs 1f\n" | |
" rjmp out\n" | |
"1: mov r30, r20\n" | |
" mov r31, r21\n" | |
" subi r30, lo8(-(jump_table/2))\n" | |
" sbci r31, hi8(-(jump_table/2))\n" | |
" ijmp\n" | |
#endif | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
avr-gcc makes really inefficient jump tables. This allows use of more efficient jump tables in c code. There's a JUMP_TABLE2 version with 2 byte entries and a JUMP_TABLE1 version with 1 byte entries. The macro should give the index and then the jump targets starting with offset 0:
You can include your own inline assembly lead-in by using the R30 variant and leaving the jump index in r30. This can be used to apply an offset or do range checking.
Note that the 1 byte entry version must have targets within 510 bytes of the entry. Optionally, a trampoline may be used.