diff --git a/backends/asm/outasm.c b/backends/asm/outasm.c index 1e1074b0e..e8b373a21 100644 --- a/backends/asm/outasm.c +++ b/backends/asm/outasm.c @@ -385,7 +385,7 @@ ValidateAbortFuncs(void) } } -static Operand *pushregs_, *popregs_, *count_, *gosub_; +static Operand *pushregs_, *popregs_, *count_, *retaddr_, *gosub_; static void ValidatePushregs(void) @@ -397,6 +397,7 @@ ValidatePushregs(void) pushregs_ = NewOperand(IMM_COG_LABEL, "pushregs_", 0); popregs_ = NewOperand(IMM_COG_LABEL, "popregs_", 0); count_ = NewOperand(REG_REG, "COUNT_", 0); + retaddr_ = NewOperand(REG_REG, "RETADDR_", 0); if (gl_p2) { frameptr = NewOperand(REG_REG, "fp", 0); } @@ -1967,7 +1968,27 @@ static void EmitFunctionHeader(IRList *irl, Function *func) FuncData(func)->numsavedregs = n; if (needFrame) { ValidateFrameptr(); - if (HUB_CODE) { + if (HUB_CODE && gl_p2 && !InCog(func) && (func->optimize_flags & OPT_INLINE_PUSHREGS)) { + // inline copy of pushregs_, building the same frame, so that + // a function in hub memory does not leave it for cog memory + // and come back, which reloads the instruction FIFO + IR *ir; + ValidatePushregs(); + EmitMove(irl, count_, NewImmediate(n), linenum); + EmitOp1(irl, OPC_POP, retaddr_); + if (n > 0) { + if (n > 1) { + EmitOp1(irl, OPC_SETQ, NewImmediate(n-1)); + } + ir = EmitOp2(irl, OPC_WRLONG, GetLocalReg(0, 0), stackptr); + ir->srceffect = OPEFFECT_POSTINC; + } + // COUNT_, RETADDR_ and fp are consecutive registers + EmitOp1(irl, OPC_SETQ, NewImmediate(2)); + ir = EmitOp2(irl, OPC_WRLONG, count_, stackptr); + ir->srceffect = OPEFFECT_POSTINC; + EmitMove(irl, frameptr, stackptr, linenum); + } else if (HUB_CODE) { ValidatePushregs(); EmitMove(irl, count_, NewImmediate(n), linenum); EmitOp1(irl, OPC_CALL, pushregs_); @@ -2014,7 +2035,22 @@ static void EmitFunctionFooter(IRList *irl, Function *func) if (!func->closure) { EmitMove(irl, stackptr, frameptr, linenum); } - if (HUB_CODE) { + if (HUB_CODE && gl_p2 && !InCog(func) && (func->optimize_flags & OPT_INLINE_PUSHREGS)) { + // inline copy of popregs_ (see EmitFunctionHeader) + IR *ir; + ValidatePushregs(); + EmitOp1(irl, OPC_SETQ, NewImmediate(2)); + ir = EmitOp2(irl, OPC_RDLONG, count_, stackptr); + ir->srceffect = OPEFFECT_PREDEC; + if (n > 0) { + if (n > 1) { + EmitOp1(irl, OPC_SETQ, NewImmediate(n-1)); + } + ir = EmitOp2(irl, OPC_RDLONG, GetLocalReg(0, 0), stackptr); + ir->srceffect = OPEFFECT_PREDEC; + } + EmitOp1(irl, OPC_PUSH, retaddr_); + } else if (HUB_CODE) { // pop registers off stack ValidatePushregs(); EmitOp1(irl, OPC_CALL, popregs_); diff --git a/cmdline.c b/cmdline.c index c31404983..758f70863 100644 --- a/cmdline.c +++ b/cmdline.c @@ -548,6 +548,7 @@ static FlagTable optflag[] = { { "spin-relax-memory", OPT_SPIN_RELAXMEM}, { "fast-inline-asm", OPT_FASTASM }, { "peek-args", OPT_PEEK_ARGS }, + { "inline-pushregs", OPT_INLINE_PUSHREGS }, { "experimental", OPT_EXPERIMENTAL }, { "all", OPT_FLAGS_ALL }, }; diff --git a/doc/general.md b/doc/general.md index 99e27e382..940deead1 100644 --- a/doc/general.md +++ b/doc/general.md @@ -462,6 +462,17 @@ used there. By default it is assumed that all argument registers may be changed within a function, but with this optimization we check for some special cases to save having to copy arguments to other registers. +### Inline register save and restore (-O2, -Oinline-pushregs) + +On the P2, a function in hub memory that needs a stack frame normally calls the +`pushregs_` and `popregs_` routines in COG memory to save and restore its +registers. With this optimization the same `setq`/`wrlong` and `setq`/`rdlong` +sequences are generated inline in the function's entry and exit code instead, +building the same stack frame. This saves about 32 cycles per call, mostly the +two trips into COG memory and back, at a cost of 5 to 9 more instructions in +each function that has a frame. Functions placed in COG or LUT memory are not +affected. + ### Common Subexpression Elimination (-O2, -Ocse) Code like: diff --git a/frontends/common.h b/frontends/common.h index de8331cdc..d0d8bebf0 100644 --- a/frontends/common.h +++ b/frontends/common.h @@ -196,6 +196,7 @@ extern int gl_optimize_flags; /* flags for optimization */ #define OPT_SPIN_RELAXMEM 0x01000000 /* relax strict memory semantics for Spin */ #define OPT_FASTASM 0x02000000 /* optimize inline assembly invocation */ #define OPT_PEEK_ARGS 0x04000000 /* peek into functions to see if arg registers can be reused */ +#define OPT_INLINE_PUSHREGS 0x08000000 /* save/restore registers inline in hub functions (P2) */ #define OPT_EXPERIMENTAL 0x80000000 /* gate new or experimental optimizations */ #define OPT_FLAGS_ALL 0xffffffff @@ -208,7 +209,7 @@ extern int gl_optimize_flags; /* flags for optimization */ // default optimization (-O1) for ASM output #define DEFAULT_ASM_OPTS (OPT_ASM_BASIC|OPT_DEADCODE|OPT_REMOVE_UNUSED_FUNCS|OPT_INLINE_SMALLFUNCS|OPT_AUTO_FCACHE|OPT_LOOP_BASIC|OPT_TAIL_CALLS|OPT_SPECIAL_FUNCS|OPT_CORDIC_REORDER|OPT_LOCAL_REUSE|OPT_LOOP_BASIC) // extras added with -O2 -#define EXTRA_ASM_OPTS (OPT_INLINE_SINGLEUSE|OPT_PERFORM_CSE|OPT_PERFORM_LOOPREDUCE|OPT_REMOVE_HUB_BSS|OPT_EXPERIMENTAL|OPT_AGGRESSIVE_MEM|OPT_MERGE_DUPLICATES|OPT_PEEK_ARGS) +#define EXTRA_ASM_OPTS (OPT_INLINE_SINGLEUSE|OPT_PERFORM_CSE|OPT_PERFORM_LOOPREDUCE|OPT_REMOVE_HUB_BSS|OPT_EXPERIMENTAL|OPT_AGGRESSIVE_MEM|OPT_MERGE_DUPLICATES|OPT_PEEK_ARGS|OPT_INLINE_PUSHREGS) // default optimization (-O1) for bytecode output; defaults to much less optimization than asm #define DEFAULT_BYTECODE_OPTS (OPT_REMOVE_UNUSED_FUNCS|OPT_REMOVE_FEATURES|OPT_DEADCODE|OPT_MAKE_MACROS|OPT_SPECIAL_FUNCS|OPT_PEEPHOLE|OPT_LOOP_BASIC)