History

Add support for arm hardfloat calling convention

See Procedure Call Standard for the ARM Architecture (AAPCS) for more details.

Thomas Preud'homme committed 14 years ago

Commit 7f6095b1 parent bfb00495 files · +330 −76Browse files

Unified · Split

5 files
  1. Changelog +1 −0
  2. Makefile +2 −0
  3. arm-gen.c +310 −70
  4. tcc.h +3 −0
  5. tccgen.c +14 −6
Changelog+1−0
@@ -14,6 +14,7 @@ not released: · 13 unmodified lines
1414 - Support indirect functions as externals (Thomas Preud'homme)
1515 - Add support for C99 variable length arrays (Thomas Preud'homme & Joe Soroka)
1616 - Improve support of ARM (Daniel Gl�ckner)
17+- Support ARM hardfloat calling convention (Thomas Preud'homme)
1718
1819 version 0.9.25:
1920
Makefile+2−0
@@ -54,6 +54,8 @@ NATIVE_DEFINES+=-DWITHOUT_LIBTCC · 53 unmodified lines
5454 NATIVE_DEFINES+=$(if $(wildcard /lib/ld-linux.so.3),-DTCC_ARM_EABI)
5555 NATIVE_DEFINES+=$(if $(wildcard /lib/arm-linux-gnueabi),-DCONFIG_MULTIARCHDIR=\"arm-linux-gnueabi\")
5656 NATIVE_DEFINES+=$(if $(shell grep -l "^Features.* \(vfp\|iwmmxt\) " /proc/cpuinfo),-DTCC_ARM_VFP)
57+# To use ARM hardfloat calling convension
58+#NATIVE_DEFINES+=-DTCC_ARM_HARDFLOAT
5759 endif
5860
5961 ifdef CONFIG_WIN32
arm-gen.c+310−70
@@ -737,16 +737,85 @@ static void gcall_or_jmp(int is_jmp) · 736 unmodified lines
737737 }
738738 }
739739
740+#ifdef TCC_ARM_HARDFLOAT
741+static int is_float_hgen_aggr(CType *type)
742+{
743+ if ((type->t & VT_BTYPE) == VT_STRUCT) {
744+ struct Sym *ref;
745+ int btype, nb_fields = 0;
746+
747+ ref = type->ref;
748+ btype = ref->type.t & VT_BTYPE;
749+ if (btype == VT_FLOAT || btype == VT_DOUBLE) {
750+ for(; ref && btype == (ref->type.t & VT_BTYPE); ref = ref->next, nb_fields++);
751+ return !ref && nb_fields <= 4;
752+ }
753+ }
754+ return 0;
755+}
756+
757+struct avail_regs {
758+ /* worst case: f(float, double, 3 float struct, double, 3 float struct, double) */
759+ signed char avail[3];
760+ int first_hole;
761+ int last_hole;
762+ int first_free_reg;
763+};
764+
765+#define AVAIL_REGS_INITIALIZER (struct avail_regs) { { 0, 0, 0}, 0, 0, 0 }
766+
767+/* Assign a register for a CPRC param with correct size and alignment
768+ * size and align are in bytes, as returned by type_size */
769+int assign_fpreg(struct avail_regs *avregs, int align, int size)
770+{
771+ int first_reg = 0;
772+
773+ if (avregs->first_free_reg == -1)
774+ return -1;
775+ if (align >> 3) { // alignment needed (base type: double)
776+ first_reg = avregs->first_free_reg;
777+ if (first_reg & 1)
778+ avregs->avail[avregs->last_hole++] = first_reg++;
779+ } else {
780+ if (size == 4 && avregs->first_hole != avregs->last_hole)
781+ return avregs->avail[avregs->first_hole++];
782+ else
783+ first_reg = avregs->first_free_reg;
784+ }
785+ if (first_reg + size / 4 <= 16) {
786+ avregs->first_free_reg = first_reg + size / 4;
787+ return first_reg;
788+ }
789+ avregs->first_free_reg = -1;
790+ return -1;
791+}
792+#endif
793+
740794 /* Generate function call. The function address is pushed first, then
741795 all the parameters in call order. This functions pops all the
742796 parameters and the function address. */
743797 void gfunc_call(int nb_args)
744798 {
745− int size, align, r, args_size, i;
746− Sym *func_sym;
799+ int size, align, r, args_size, i, ncrn, ncprn, argno, vfp_argno;
747800 signed char plan[4][2]={{-1,-1},{-1,-1},{-1,-1},{-1,-1}};
748− int todo=0xf, keep, plan2[4]={0,0,0,0};
801+ SValue *before_stack = NULL; /* SValue before first on stack argument */
802+ SValue *before_vfpreg_hfa = NULL; /* SValue before first in VFP reg hfa argument */
803+#ifdef TCC_ARM_HARDFLOAT
804+ struct avail_regs avregs = AVAIL_REGS_INITIALIZER;
805+ signed char vfp_plan[16];
806+ int plan2[4+16];
807+ int variadic;
808+#else
809+ int plan2[4]={0,0,0,0};
810+#endif
811+ int vfp_todo=0;
812+ int todo=0, keep;
749813
814+#ifdef TCC_ARM_HARDFLOAT
815+ memset(vfp_plan, -1, sizeof(vfp_plan));
816+ memset(plan2, 0, sizeof(plan2));
817+ variadic = (vtop[-nb_args].type.ref->c == FUNC_ELLIPSIS);
818+#endif
750819 r = vtop->r & VT_VALMASK;
751820 if (r == VT_CMP || (r & ~1) == VT_JMP)
752821 gv(RC_INT);
@@ -763,39 +832,128 @@ void gfunc_call(int nb_args) · 10 unmodified lines
763832 vpushi(0);
764833 vtop->type.t = VT_LLONG;
765834 args_size = 0;
835+#endif
836+ ncrn = ncprn = argno = vfp_argno = 0;
837+ /* Assign argument to registers and stack with alignment.
838+ If, considering alignment constraints, enough registers of the correct type
839+ (core or VFP) are free for the current argument, assign them to it, else
840+ allocate on stack with correct alignment. Whenever a structure is allocated
841+ in registers or on stack, it is always put on the stack at this stage. The
842+ stack is divided in 3 zones. The zone are, from low addresses to high
843+ addresses: structures to be loaded in core registers, structures to be
844+ loaded in VFP registers, argument allocated to stack. SValue's representing
845+ structures in the first zone are moved just after the SValue pointed by
846+ before_vfpreg_hfa. SValue's representing structures in the second zone are
847+ moved just after the SValue pointer by before_stack. */
766848 for(i = nb_args + 1 ; i-- ;) {
849+ int j, assigned_vfpreg = 0;
767850 size = type_size(&vtop[-i].type, &align);
768− if(args_size & (align-1)) {
769− vpushi(0);
770− vtop->type.t = VT_VOID; /* padding */
771− vrott(i+2);
772− args_size += 4;
773− ++nb_args;
774− }
775− args_size += (size + 3) & -4;
776− }
777− vtop--;
778−#endif
779− args_size = 0;
780− for(i = nb_args ; i-- && args_size < 16 ;) {
781851 switch(vtop[-i].type.t & VT_BTYPE) {
782852 case VT_STRUCT:
783853 case VT_FLOAT:
784854 case VT_DOUBLE:
785855 case VT_LDOUBLE:
786− size = type_size(&vtop[-i].type, &align);
787− size = (size + 3) & -4;
788− args_size += size;
856+#ifdef TCC_ARM_HARDFLOAT
857+ if (!variadic) {
858+ int hfa = 0; /* Homogeneous float aggregate */
859+
860+ if (is_float(vtop[-i].type.t)
861+ || (hfa = is_float_hgen_aggr(&vtop[-i].type))) {
862+ int end_reg;
863+
864+ assigned_vfpreg = assign_fpreg(&avregs, align, size);
865+ end_reg = assigned_vfpreg + (size - 1) / 4;
866+ if (assigned_vfpreg >= 0) {
867+ vfp_plan[vfp_argno++]=TREG_F0 + assigned_vfpreg/2;
868+ if (hfa) {
869+ /* before_stack can only have been set because all core registers
870+ are assigned, so no need to care about before_vfpreg_hfa if
871+ before_stack is set */
872+ if (before_stack) {
873+ vrote(&vtop[-i], &vtop[-i] - before_stack);
874+ before_stack++;
875+ } else if (!before_vfpreg_hfa)
876+ before_vfpreg_hfa = &vtop[-i-1];
877+ for (j = assigned_vfpreg; j <= end_reg; j++)
878+ vfp_todo|=(1<<j);
879+ }
880+ continue;
881+ } else {
882+ if (!hfa)
883+ vfp_argno++;
884+ /* No need to update before_stack as no more hfa can be allocated in
885+ VFP regs */
886+ if (!before_vfpreg_hfa)
887+ before_vfpreg_hfa = &vtop[-i-1];
888+ break;
889+ }
890+ }
891+ }
892+#endif
893+ ncrn = (ncrn + (align-1)/4) & -(align/4);
894+ size = (size + 3) & -4;
895+ if (ncrn + size/4 <= 4 || (ncrn < 4 && assigned_vfpreg != -1)) {
896+ /* Either there is HFA in VFP registers, or there is arguments on stack,
897+ it cannot be both. Hence either before_stack already points after
898+ the slot where the vtop[-i] SValue is moved, or before_stack will not
899+ be used */
900+ if (before_vfpreg_hfa) {
901+ vrote(&vtop[-i], &vtop[-i] - before_vfpreg_hfa);
902+ before_vfpreg_hfa++;
903+ }
904+ for (j = ncrn; j < 4 && j < ncrn + size / 4; j++)
905+ todo|=(1<<j);
906+ ncrn+=size/4;
907+ if (ncrn > 4) {
908+ args_size = (ncrn - 4) * 4;
909+ if (!before_stack)
910+ before_stack = &vtop[-i-1];
911+ }
912+ }
913+ else {
914+ ncrn = 4;
915+ /* No need to set before_vfpreg_hfa if not set since there will no
916+ longer be any structure assigned to core registers */
917+ if (!before_stack)
918+ before_stack = &vtop[-i-1];
789919 break;
920+ }
921+ continue;
790922 default:
791− plan[nb_args-1-i][0]=args_size/4;
792− args_size += 4;
793− if ((vtop[-i].type.t & VT_BTYPE) == VT_LLONG && args_size < 16) {
794− plan[nb_args-1-i][1]=args_size/4;
795− args_size += 4;
923+ if (!i) {
924+ break;
796925 }
926+ if (ncrn < 4) {
927+ int is_long = (vtop[-i].type.t & VT_BTYPE) == VT_LLONG;
928+
929+ if (is_long) {
930+ ncrn = (ncrn + 1) & -2;
931+ if (ncrn == 4) {
932+ argno++;
933+ break;
934+ }
935+ }
936+ plan[argno++][0]=ncrn++;
937+ if (is_long) {
938+ plan[argno-1][1]=ncrn++;
939+ }
940+ continue;
941+ }
942+ argno++;
797943 }
944+#ifdef TCC_ARM_EABI
945+ if(args_size & (align-1)) {
946+ vpushi(0);
947+ vtop->type.t = VT_VOID; /* padding */
948+ vrott(i+2);
949+ args_size += 4;
950+ nb_args++;
951+ argno++;
952+ }
953+#endif
954+ args_size += (size + 3) & -4;
798955 }
956+ vtop--;
799957 args_size = keep = 0;
800958 for(i = 0;i < nb_args; i++) {
801959 vrotb(keep+1);
@@ -814,6 +972,12 @@ void gfunc_call(int nb_args) · 12 unmodified lines
814972 vtop--;
815973 args_size += size;
816974 } else if (is_float(vtop->type.t)) {
975+#ifdef TCC_ARM_HARDFLOAT
976+ if (!variadic && --vfp_argno<16 && vfp_plan[vfp_argno]!=-1) {
977+ plan2[keep++]=vfp_plan[vfp_argno];
978+ continue;
979+ }
980+#endif
817981 #ifdef TCC_ARM_VFP
818982 r=vfpr(gv(RC_FLOAT))<<12;
819983 size=4;
@@ -848,57 +1012,59 @@ void gfunc_call(int nb_args) · 28 unmodified lines
8481012 size=4;
8491013 if ((vtop->type.t & VT_BTYPE) == VT_LLONG) {
8501014 lexpand_nr();
851− s=RC_INT;
852− if(nb_args-i<5 && plan[nb_args-i-1][1]!=-1) {
853− s=regmask(plan[nb_args-i-1][1]);
854− todo&=~(1<<plan[nb_args-i-1][1]);
855− }
856− if(s==RC_INT) {
857− r = gv(s);
1015+ s=-1;
1016+ if(--argno<4 && plan[argno][1]!=-1)
1017+ s=plan[argno][1];
1018+ argno++;
1019+ size = 8;
1020+ if(s==-1) {
1021+ r = gv(RC_INT);
8581022 o(0xE52D0004|(intr(r)<<12)); /* str r,[sp,#-4]! */
8591023 vtop--;
8601024 } else {
1025+ size=0;
8611026 plan2[keep]=s;
8621027 keep++;
8631028 vswap();
8641029 }
865− size = 8;
866− }
867− s=RC_INT;
868− if(nb_args-i<5 && plan[nb_args-i-1][0]!=-1) {
869− s=regmask(plan[nb_args-i-1][0]);
870− todo&=~(1<<plan[nb_args-i-1][0]);
8711030 }
1031+ s=-1;
1032+ if(--argno<4 && plan[argno][0]!=-1)
1033+ s=plan[argno][0];
8721034 #ifdef TCC_ARM_EABI
8731035 if(vtop->type.t == VT_VOID) {
874− if(s == RC_INT)
1036+ if(s == -1)
8751037 o(0xE24DD004); /* sub sp,sp,#4 */
8761038 vtop--;
8771039 } else
878−#endif
879− if(s == RC_INT) {
880− r = gv(s);
1040+#endif
1041+ if(s == -1) {
1042+ r = gv(RC_INT);
8811043 o(0xE52D0004|(intr(r)<<12)); /* str r,[sp,#-4]! */
8821044 vtop--;
8831045 } else {
1046+ size=0;
8841047 plan2[keep]=s;
8851048 keep++;
8861049 }
8871050 args_size += size;
8881051 }
8891052 }
890− for(i=keep;i--;) {
891− gv(plan2[i]);
892− vrott(keep);
1053+ for(i = 0; i < keep; i++) {
1054+ vrotb(keep);
1055+ gv(regmask(plan2[i]));
1056+ /* arg is in s(2d+1): plan2[i]<plan2[i+1] => alignment occured (ex f,d,f) */
1057+ if (i < keep - 1 && is_float(vtop->type.t) && (plan2[i] <= plan2[i + 1])) {
1058+ o(0xEEF00A40|(vfpr(plan2[i])<<12)|vfpr(plan2[i]));
1059+ }
8931060 }
8941061 save_regs(keep); /* save used temporary registers */
8951062 keep++;
896− if(args_size) {
897− int n;
898− n=args_size/4;
899− if(n>4)
900− n=4;
901− todo&=((1<<n)-1);
1063+ if(ncrn) {
1064+ int nb_regs=0;
1065+ if (ncrn>4)
1066+ ncrn=4;
1067+ todo&=((1<<ncrn)-1);
9021068 if(todo) {
9031069 int i;
9041070 o(0xE8BD0000|todo);
@@ -907,12 +1073,31 @@ save_regs(keep); /* save used temporary registers */ · 2 unmodified lines
9071073 vpushi(0);
9081074 vtop->r=i;
9091075 keep++;
1076+ nb_regs++;
9101077 }
9111078 }
912− args_size-=n*4;
1079+ args_size-=nb_regs*4;
1080+ }
1081+ if(vfp_todo) {
1082+ int nb_fregs=0;
1083+
1084+ for(i=0;i<16;i++)
1085+ if(vfp_todo&(1<<i)) {
1086+ o(0xED9D0A00|(i&1)<<22|(i>>1)<<12|nb_fregs);
1087+ vpushi(0);
1088+ /* There might be 2 floats in a double VFP reg but that doesn't seem
1089+ to matter */
1090+ if (!(i%2))
1091+ vtop->r=TREG_F0+i/2;
1092+ keep++;
1093+ nb_fregs++;
1094+ }
1095+ if (nb_fregs) {
1096+ gadd_sp(nb_fregs*4);
1097+ args_size-=nb_fregs*4;
1098+ }
9131099 }
9141100 vrotb(keep);
915− func_sym = vtop->type.ref;
9161101 gcall_or_jmp(0);
9171102 if (args_size)
9181103 gadd_sp(args_size);
@@ -924,7 +1109,11 @@ save_regs(keep); /* save used temporary registers */ · 5 unmodified lines
9241109 ++keep;
9251110 }
9261111 #ifdef TCC_ARM_VFP
1112+#ifdef TCC_ARM_HARDFLOAT
1113+ else if(variadic && is_float(vtop->type.ref->type.t)) {
1114+#else
9271115 else if(is_float(vtop->type.ref->type.t)) {
1116+#endif
9281117 if((vtop->type.ref->type.t & VT_BTYPE) == VT_FLOAT) {
9291118 o(0xEE000A10); /* fmsr s0,r0 */
9301119 } else {
@@ -942,26 +1131,38 @@ save_regs(keep); /* save used temporary registers */ · 11 unmodified lines
9421131 void gfunc_prolog(CType *func_type)
9431132 {
9441133 Sym *sym,*sym2;
945− int n,addr,size,align;
1134+ int n,nf,size,align, variadic, struct_ret = 0;
1135+#ifdef TCC_ARM_HARDFLOAT
1136+ struct avail_regs avregs = AVAIL_REGS_INITIALIZER;
1137+#endif
9461138
9471139 sym = func_type->ref;
9481140 func_vt = sym->type;
949−
950− n = 0;
951− addr = 0;
1141+
1142+ n = nf = 0;
1143+ variadic = (func_type->ref->c == FUNC_ELLIPSIS);
9521144 if((func_vt.t & VT_BTYPE) == VT_STRUCT
9531145 && type_size(&func_vt,&align) > 4)
9541146 {
955− func_vc = addr;
956− addr += 4;
9571147 n++;
1148+ struct_ret = 1;
9581149 }
959− for(sym2=sym->next;sym2 && n<4;sym2=sym2->next) {
1150+ for(sym2=sym->next;sym2 && (n<4 || nf<16);sym2=sym2->next) {
9601151 size = type_size(&sym2->type, &align);
961− n += (size + 3) / 4;
1152+#ifdef TCC_ARM_HARDFLOAT
1153+ if (!variadic && (is_float(sym2->type.t)
1154+ || is_float_hgen_aggr(&sym2->type))) {
1155+ int tmpnf = assign_fpreg(&avregs, align, size) + 1;
1156+ nf = (tmpnf > nf) ? tmpnf : nf;
1157+ } else
1158+#endif
1159+ if (n < 4)
1160+ n += (size + 3) / 4;
9621161 }
1162+ if (struct_ret)
1163+ func_vc = nf * 4;
9631164 o(0xE1A0C00D); /* mov ip,sp */
964− if(func_type->ref->c == FUNC_ELLIPSIS)
1165+ if(variadic)
9651166 n=4;
9661167 if(n) {
9671168 if(n>4)
@@ -971,20 +1172,57 @@ void gfunc_prolog(CType *func_type) · 3 unmodified lines
9711172 #endif
9721173 o(0xE92D0000|((1<<n)-1)); /* save r0-r4 on stack if needed */
9731174 }
1175+ if (nf) {
1176+ if (nf>16)
1177+ nf=16;
1178+ nf=(nf+1)&-2; /* nf => HARDFLOAT => EABI */
1179+ o(0xED2D0A00|nf); /* save s0-s15 on stack if needed */
1180+ }
9741181 o(0xE92D5800); /* save fp, ip, lr */
9751182 o(0xE28DB00C); /* add fp, sp, #12 */
9761183 func_sub_sp_offset = ind;
977− o(0xE1A00000); /* nop, leave space for stack adjustment */
978− while ((sym = sym->next)) {
979− CType *type;
980− type = &sym->type;
981− size = type_size(type, &align);
982− size = (size + 3) & -4;
1184+ o(0xE1A00000); /* nop, leave space for stack adjustment in epilogue */
1185+ {
1186+ int addr, pn = struct_ret, sn = 0; /* pn=core, sn=stack */
1187+
1188+#ifdef TCC_ARM_HARDFLOAT
1189+ avregs = AVAIL_REGS_INITIALIZER;
1190+#endif
1191+ while ((sym = sym->next)) {
1192+ CType *type;
1193+ type = &sym->type;
1194+ size = type_size(type, &align);
1195+ size = (size + 3) >> 2;
1196+#ifdef TCC_ARM_HARDFLOAT
1197+ if (!variadic && (is_float(sym->type.t)
1198+ || is_float_hgen_aggr(&sym->type))) {
1199+ int fpn = assign_fpreg(&avregs, align, size << 2);
1200+ if (fpn >= 0) {
1201+ addr = fpn * 4;
1202+ } else
1203+ goto from_stack;
1204+ } else
1205+#endif
1206+ if (pn < 4) {
1207+#ifdef TCC_ARM_EABI
1208+ pn = (pn + (align-1)/4) & -(align/4);
1209+#endif
1210+ addr = (nf + pn) * 4;
1211+ pn += size;
1212+ if (!sn && pn > 4)
1213+ sn = (pn - 4);
1214+ } else {
1215+#ifdef TCC_ARM_HARDFLOAT
1216+from_stack:
1217+#endif
9831218 #ifdef TCC_ARM_EABI
984− addr = (addr + align - 1) & -align;
1219+ sn = (sn + (align-1)/4) & -(align/4);
9851220 #endif
986− sym_push(sym->v & ~SYM_FIELD, type, VT_LOCAL | lvalue_type(type->t), addr);
987− addr += size;
1221+ addr = (n + nf + sn) * 4;
1222+ sn += size;
1223+ }
1224+ sym_push(sym->v & ~SYM_FIELD, type, VT_LOCAL | lvalue_type(type->t), addr);
1225+ }
9881226 }
9891227 last_itod_magic=0;
9901228 leaffunc = 1;
@@ -997,6 +1235,8 @@ void gfunc_epilog(void) · 6 unmodified lines
9971235 uint32_t x;
9981236 int diff;
9991237 #ifdef TCC_ARM_EABI
1238+ /* Useless but harmless copy of the float result into main register(s) in case
1239+ of variadic function in the hardfloat variant */
10001240 if(is_float(func_vt.t)) {
10011241 if((func_vt.t & VT_BTYPE) == VT_FLOAT)
10021242 o(0xEE100A10); /* fmrs r0, s0 */
tcc.h+3−0
@@ -204,6 +204,8 @@ · 203 unmodified lines
204204 # define CONFIG_TCC_ELFINTERP "/libexec/ld-elf.so.1"
205205 # elif defined __FreeBSD_kernel__
206206 # define CONFIG_TCC_ELFINTERP "/lib/ld.so.1"
207+# elif defined TCC_ARM_HARDFLOAT
208+# define CONFIG_TCC_ELFINTERP "/lib/ld-linux-armhf.so.3"
207209 # elif defined TCC_ARM_EABI
208210 # define CONFIG_TCC_ELFINTERP "/lib/ld-linux.so.3"
209211 # elif defined(TCC_TARGET_X86_64)
@@ -1138,6 +1140,7 @@ ST_FUNC Sym *external_global_sym(int v, CType *type, int r); · 928 unmodified lines
11381140 ST_FUNC void vset(CType *type, int r, int v);
11391141 ST_FUNC void vswap(void);
11401142 ST_FUNC void vpush_global_sym(CType *type, int v);
1143+ST_FUNC void vrote(SValue *e, int n);
11411144 ST_FUNC void vrott(int n);
11421145 ST_FUNC void vrotb(int n);
11431146 #ifdef TCC_TARGET_ARM
tccgen.c+14−6
@@ -972,18 +972,26 @@ ST_FUNC void vrotb(int n) · 971 unmodified lines
972972 vtop[0] = tmp;
973973 }
974974
975−/* rotate n first stack elements to the top
976− I1 ... In -> In I1 ... I(n-1) [top is right]
975+/* rotate the n elements before entry e towards the top
976+ I1 ... In ... -> In I1 ... I(n-1) ... [top is right]
977977 */
978−ST_FUNC void vrott(int n)
978+ST_FUNC void vrote(SValue *e, int n)
979979 {
980980 int i;
981981 SValue tmp;
982982
983− tmp = vtop[0];
983+ tmp = *e;
984984 for(i = 0;i < n - 1; i++)
985− vtop[-i] = vtop[-i - 1];
986− vtop[-n + 1] = tmp;
985+ e[-i] = e[-i - 1];
986+ e[-n + 1] = tmp;
987+}
988+
989+/* rotate n first stack elements to the top
990+ I1 ... In -> In I1 ... I(n-1) [top is right]
991+ */
992+ST_FUNC void vrott(int n)
993+{
994+ vrote(vtop, n);
987995 }
988996
989997 /* pop stack value */

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t