Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(162283)

Side by Side Diff: Modules/_ctypes/libffi_ios/x86/ffi_i386.c

Issue 23670: Modifications to support iOS as a development platform
Patch Set: Created 3 years, 9 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #ifdef __i386__
2
3 /* -----------------------------------------------------------------------
4 ffi.c - Copyright (c) 1996, 1998, 1999, 2001, 2007, 2008 Red Hat, Inc.
5 Copyright (c) 2002 Ranjit Mathew
6 Copyright (c) 2002 Bo Thorsen
7 Copyright (c) 2002 Roger Sayle
8 Copyright (C) 2008, 2010 Free Software Foundation, Inc.
9
10 x86 Foreign Function Interface
11
12 Permission is hereby granted, free of charge, to any person obtaining
13 a copy of this software and associated documentation files (the
14 ``Software''), to deal in the Software without restriction, including
15 without limitation the rights to use, copy, modify, merge, publish,
16 distribute, sublicense, and/or sell copies of the Software, and to
17 permit persons to whom the Software is furnished to do so, subject to
18 the following conditions:
19
20 The above copyright notice and this permission notice shall be included
21 in all copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
24 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
27 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
28 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30 DEALINGS IN THE SOFTWARE.
31 ----------------------------------------------------------------------- */
32
33 #ifndef __x86_64__
34 #include <ffi.h>
35 #include <ffi_common.h>
36 #include <stdlib.h>
37 #include "internal.h"
38
39 /* Force FFI_TYPE_LONGDOUBLE to be different than FFI_TYPE_DOUBLE;
40 all further uses in this file will refer to the 80-bit type. */
41 #if FFI_TYPE_LONGDOUBLE != FFI_TYPE_DOUBLE
42 # if FFI_TYPE_LONGDOUBLE != 4
43 # error FFI_TYPE_LONGDOUBLE out of date
44 # endif
45 #else
46 # undef FFI_TYPE_LONGDOUBLE
47 # define FFI_TYPE_LONGDOUBLE 4
48 #endif
49
50 #if defined(__GNUC__) && !defined(__declspec)
51 # define __declspec(x) __attribute__((x))
52 #endif
53
54 /* Perform machine dependent cif processing. */
55 ffi_status FFI_HIDDEN
56 ffi_prep_cif_machdep(ffi_cif *cif)
57 {
58 size_t bytes = 0;
59 int i, n, flags, cabi = cif->abi;
60
61 switch (cabi)
62 {
63 case FFI_SYSV:
64 case FFI_STDCALL:
65 case FFI_THISCALL:
66 case FFI_FASTCALL:
67 case FFI_MS_CDECL:
68 case FFI_PASCAL:
69 case FFI_REGISTER:
70 break;
71 default:
72 return FFI_BAD_ABI;
73 }
74
75 switch (cif->rtype->type)
76 {
77 case FFI_TYPE_VOID:
78 flags = X86_RET_VOID;
79 break;
80 case FFI_TYPE_FLOAT:
81 flags = X86_RET_FLOAT;
82 break;
83 case FFI_TYPE_DOUBLE:
84 flags = X86_RET_DOUBLE;
85 break;
86 case FFI_TYPE_LONGDOUBLE:
87 flags = X86_RET_LDOUBLE;
88 break;
89 case FFI_TYPE_UINT8:
90 flags = X86_RET_UINT8;
91 break;
92 case FFI_TYPE_UINT16:
93 flags = X86_RET_UINT16;
94 break;
95 case FFI_TYPE_SINT8:
96 flags = X86_RET_SINT8;
97 break;
98 case FFI_TYPE_SINT16:
99 flags = X86_RET_SINT16;
100 break;
101 case FFI_TYPE_INT:
102 case FFI_TYPE_SINT32:
103 case FFI_TYPE_UINT32:
104 case FFI_TYPE_POINTER:
105 flags = X86_RET_INT32;
106 break;
107 case FFI_TYPE_SINT64:
108 case FFI_TYPE_UINT64:
109 flags = X86_RET_INT64;
110 break;
111 case FFI_TYPE_STRUCT:
112 #ifndef X86
113 /* ??? This should be a different ABI rather than an ifdef. */
114 if (cif->rtype->size == 1)
115 flags = X86_RET_STRUCT_1B;
116 else if (cif->rtype->size == 2)
117 flags = X86_RET_STRUCT_2B;
118 else if (cif->rtype->size == 4)
119 flags = X86_RET_INT32;
120 else if (cif->rtype->size == 8)
121 flags = X86_RET_INT64;
122 else
123 #endif
124 {
125 do_struct:
126 switch (cabi)
127 {
128 case FFI_THISCALL:
129 case FFI_FASTCALL:
130 case FFI_STDCALL:
131 case FFI_MS_CDECL:
132 flags = X86_RET_STRUCTARG;
133 break;
134 default:
135 flags = X86_RET_STRUCTPOP;
136 break;
137 }
138 /* Allocate space for return value pointer. */
139 bytes += ALIGN (sizeof(void*), FFI_SIZEOF_ARG);
140 }
141 break;
142 case FFI_TYPE_COMPLEX:
143 switch (cif->rtype->elements[0]->type)
144 {
145 case FFI_TYPE_DOUBLE:
146 case FFI_TYPE_LONGDOUBLE:
147 case FFI_TYPE_SINT64:
148 case FFI_TYPE_UINT64:
149 goto do_struct;
150 case FFI_TYPE_FLOAT:
151 case FFI_TYPE_INT:
152 case FFI_TYPE_SINT32:
153 case FFI_TYPE_UINT32:
154 flags = X86_RET_INT64;
155 break;
156 case FFI_TYPE_SINT16:
157 case FFI_TYPE_UINT16:
158 flags = X86_RET_INT32;
159 break;
160 case FFI_TYPE_SINT8:
161 case FFI_TYPE_UINT8:
162 flags = X86_RET_STRUCT_2B;
163 break;
164 default:
165 return FFI_BAD_TYPEDEF;
166 }
167 break;
168 default:
169 return FFI_BAD_TYPEDEF;
170 }
171 cif->flags = flags;
172
173 for (i = 0, n = cif->nargs; i < n; i++)
174 {
175 ffi_type *t = cif->arg_types[i];
176
177 bytes = ALIGN (bytes, t->alignment);
178 bytes += ALIGN (t->size, FFI_SIZEOF_ARG);
179 }
180 cif->bytes = ALIGN (bytes, 16);
181
182 return FFI_OK;
183 }
184
185 static ffi_arg
186 extend_basic_type(void *arg, int type)
187 {
188 switch (type)
189 {
190 case FFI_TYPE_SINT8:
191 return *(SINT8 *)arg;
192 case FFI_TYPE_UINT8:
193 return *(UINT8 *)arg;
194 case FFI_TYPE_SINT16:
195 return *(SINT16 *)arg;
196 case FFI_TYPE_UINT16:
197 return *(UINT16 *)arg;
198
199 case FFI_TYPE_SINT32:
200 case FFI_TYPE_UINT32:
201 case FFI_TYPE_POINTER:
202 case FFI_TYPE_FLOAT:
203 return *(UINT32 *)arg;
204
205 default:
206 abort();
207 }
208 }
209
210 struct call_frame
211 {
212 void *ebp; /* 0 */
213 void *retaddr; /* 4 */
214 void (*fn)(void); /* 8 */
215 int flags; /* 12 */
216 void *rvalue; /* 16 */
217 unsigned regs[3]; /* 20-28 */
218 };
219
220 struct abi_params
221 {
222 int dir; /* parameter growth direction */
223 int static_chain; /* the static chain register used by gcc */
224 int nregs; /* number of register parameters */
225 int regs[3];
226 };
227
228 static const struct abi_params abi_params[FFI_LAST_ABI] = {
229 [FFI_SYSV] = { 1, R_ECX, 0 },
230 [FFI_THISCALL] = { 1, R_EAX, 1, { R_ECX } },
231 [FFI_FASTCALL] = { 1, R_EAX, 2, { R_ECX, R_EDX } },
232 [FFI_STDCALL] = { 1, R_ECX, 0 },
233 [FFI_PASCAL] = { -1, R_ECX, 0 },
234 /* ??? No defined static chain; gcc does not support REGISTER. */
235 [FFI_REGISTER] = { -1, R_ECX, 3, { R_EAX, R_EDX, R_ECX } },
236 [FFI_MS_CDECL] = { 1, R_ECX, 0 }
237 };
238
239 extern void ffi_call_i386(struct call_frame *, char *)
240 #if HAVE_FASTCALL
241 __declspec(fastcall)
242 #endif
243 FFI_HIDDEN;
244
245 static void
246 ffi_call_int (ffi_cif *cif, void (*fn)(void), void *rvalue,
247 void **avalue, void *closure)
248 {
249 size_t rsize, bytes;
250 struct call_frame *frame;
251 char *stack, *argp;
252 ffi_type **arg_types;
253 int flags, cabi, i, n, dir, narg_reg;
254 const struct abi_params *pabi;
255
256 flags = cif->flags;
257 cabi = cif->abi;
258 pabi = &abi_params[cabi];
259 dir = pabi->dir;
260
261 rsize = 0;
262 if (rvalue == NULL)
263 {
264 switch (flags)
265 {
266 case X86_RET_FLOAT:
267 case X86_RET_DOUBLE:
268 case X86_RET_LDOUBLE:
269 case X86_RET_STRUCTPOP:
270 case X86_RET_STRUCTARG:
271 /* The float cases need to pop the 387 stack.
272 The struct cases need to pass a valid pointer to the callee. */
273 rsize = cif->rtype->size;
274 break;
275 default:
276 /* We can pretend that the callee returns nothing. */
277 flags = X86_RET_VOID;
278 break;
279 }
280 }
281
282 bytes = cif->bytes;
283 stack = alloca(bytes + sizeof(*frame) + rsize);
284 argp = (dir < 0 ? stack + bytes : stack);
285 frame = (struct call_frame *)(stack + bytes);
286 if (rsize)
287 rvalue = frame + 1;
288
289 frame->fn = fn;
290 frame->flags = flags;
291 frame->rvalue = rvalue;
292 frame->regs[pabi->static_chain] = (unsigned)closure;
293
294 narg_reg = 0;
295 switch (flags)
296 {
297 case X86_RET_STRUCTARG:
298 /* The pointer is passed as the first argument. */
299 if (pabi->nregs > 0)
300 {
301 frame->regs[pabi->regs[0]] = (unsigned)rvalue;
302 narg_reg = 1;
303 break;
304 }
305 /* fallthru */
306 case X86_RET_STRUCTPOP:
307 *(void **)argp = rvalue;
308 argp += sizeof(void *);
309 break;
310 }
311
312 arg_types = cif->arg_types;
313 for (i = 0, n = cif->nargs; i < n; i++)
314 {
315 ffi_type *ty = arg_types[i];
316 void *valp = avalue[i];
317 size_t z = ty->size;
318 int t = ty->type;
319
320 if (z <= FFI_SIZEOF_ARG && t != FFI_TYPE_STRUCT)
321 {
322 ffi_arg val = extend_basic_type (valp, t);
323
324 if (t != FFI_TYPE_FLOAT && narg_reg < pabi->nregs)
325 frame->regs[pabi->regs[narg_reg++]] = val;
326 else if (dir < 0)
327 {
328 argp -= 4;
329 *(ffi_arg *)argp = val;
330 }
331 else
332 {
333 *(ffi_arg *)argp = val;
334 argp += 4;
335 }
336 }
337 else
338 {
339 size_t za = ALIGN (z, FFI_SIZEOF_ARG);
340 size_t align = FFI_SIZEOF_ARG;
341
342 /* Alignment rules for arguments are quite complex. Vectors and
343 structures with 16 byte alignment get it. Note that long double
344 on Darwin does have 16 byte alignment, and does not get this
345 alignment if passed directly; a structure with a long double
346 inside, however, would get 16 byte alignment. Since libffi does
347 not support vectors, we need non concern ourselves with other
348 cases. */
349 if (t == FFI_TYPE_STRUCT && ty->alignment >= 16)
350 align = 16;
351
352 if (dir < 0)
353 {
354 /* ??? These reverse argument ABIs are probably too old
355 to have cared about alignment. Someone should check. */
356 argp -= za;
357 memcpy (argp, valp, z);
358 }
359 else
360 {
361 argp = (char *)ALIGN (argp, align);
362 memcpy (argp, valp, z);
363 argp += za;
364 }
365 }
366 }
367 FFI_ASSERT (dir > 0 || argp == stack);
368
369 ffi_call_i386 (frame, stack);
370 }
371
372 void
373 ffi_call (ffi_cif *cif, void (*fn)(void), void *rvalue, void **avalue)
374 {
375 ffi_call_int (cif, fn, rvalue, avalue, NULL);
376 }
377
378 void
379 ffi_call_go (ffi_cif *cif, void (*fn)(void), void *rvalue,
380 void **avalue, void *closure)
381 {
382 ffi_call_int (cif, fn, rvalue, avalue, closure);
383 }
384
385 /** private members **/
386
387 void FFI_HIDDEN ffi_closure_i386(void);
388 void FFI_HIDDEN ffi_closure_STDCALL(void);
389 void FFI_HIDDEN ffi_closure_REGISTER(void);
390
391 struct closure_frame
392 {
393 unsigned rettemp[4]; /* 0 */
394 unsigned regs[3]; /* 16-24 */
395 ffi_cif *cif; /* 28 */
396 void (*fun)(ffi_cif*,void*,void**,void*); /* 32 */
397 void *user_data; /* 36 */
398 };
399
400 int FFI_HIDDEN
401 #if HAVE_FASTCALL
402 __declspec(fastcall)
403 #endif
404 ffi_closure_inner (struct closure_frame *frame, char *stack)
405 {
406 ffi_cif *cif = frame->cif;
407 int cabi, i, n, flags, dir, narg_reg;
408 const struct abi_params *pabi;
409 ffi_type **arg_types;
410 char *argp;
411 void *rvalue;
412 void **avalue;
413
414 cabi = cif->abi;
415 flags = cif->flags;
416 narg_reg = 0;
417 rvalue = frame->rettemp;
418 pabi = &abi_params[cabi];
419 dir = pabi->dir;
420 argp = (dir < 0 ? stack + cif->bytes : stack);
421
422 switch (flags)
423 {
424 case X86_RET_STRUCTARG:
425 if (pabi->nregs > 0)
426 {
427 rvalue = (void *)frame->regs[pabi->regs[0]];
428 narg_reg = 1;
429 frame->rettemp[0] = (unsigned)rvalue;
430 break;
431 }
432 /* fallthru */
433 case X86_RET_STRUCTPOP:
434 rvalue = *(void **)argp;
435 argp += sizeof(void *);
436 frame->rettemp[0] = (unsigned)rvalue;
437 break;
438 }
439
440 n = cif->nargs;
441 avalue = alloca(sizeof(void *) * n);
442
443 arg_types = cif->arg_types;
444 for (i = 0; i < n; ++i)
445 {
446 ffi_type *ty = arg_types[i];
447 size_t z = ty->size;
448 int t = ty->type;
449 void *valp;
450
451 if (z <= FFI_SIZEOF_ARG && t != FFI_TYPE_STRUCT)
452 {
453 if (t != FFI_TYPE_FLOAT && narg_reg < pabi->nregs)
454 valp = &frame->regs[pabi->regs[narg_reg++]];
455 else if (dir < 0)
456 {
457 argp -= 4;
458 valp = argp;
459 }
460 else
461 {
462 valp = argp;
463 argp += 4;
464 }
465 }
466 else
467 {
468 size_t za = ALIGN (z, FFI_SIZEOF_ARG);
469 size_t align = FFI_SIZEOF_ARG;
470
471 /* See the comment in ffi_call_int. */
472 if (t == FFI_TYPE_STRUCT && ty->alignment >= 16)
473 align = 16;
474
475 if (dir < 0)
476 {
477 /* ??? These reverse argument ABIs are probably too old
478 to have cared about alignment. Someone should check. */
479 argp -= za;
480 valp = argp;
481 }
482 else
483 {
484 argp = (char *)ALIGN (argp, align);
485 valp = argp;
486 argp += za;
487 }
488 }
489
490 avalue[i] = valp;
491 }
492
493 frame->fun (cif, rvalue, avalue, frame->user_data);
494
495 if (cabi == FFI_STDCALL)
496 return flags + (cif->bytes << X86_RET_POP_SHIFT);
497 else
498 return flags;
499 }
500
501 ffi_status
502 ffi_prep_closure_loc (ffi_closure* closure,
503 ffi_cif* cif,
504 void (*fun)(ffi_cif*,void*,void**,void*),
505 void *user_data,
506 void *codeloc)
507 {
508 char *tramp = closure->tramp;
509 void (*dest)(void);
510 int op = 0xb8; /* movl imm, %eax */
511
512 switch (cif->abi)
513 {
514 case FFI_SYSV:
515 case FFI_THISCALL:
516 case FFI_FASTCALL:
517 case FFI_MS_CDECL:
518 dest = ffi_closure_i386;
519 break;
520 case FFI_STDCALL:
521 case FFI_PASCAL:
522 dest = ffi_closure_STDCALL;
523 break;
524 case FFI_REGISTER:
525 dest = ffi_closure_REGISTER;
526 op = 0x68; /* pushl imm */
527 default:
528 return FFI_BAD_ABI;
529 }
530
531 /* movl or pushl immediate. */
532 tramp[0] = op;
533 *(void **)(tramp + 1) = codeloc;
534
535 /* jmp dest */
536 tramp[5] = 0xe9;
537 *(unsigned *)(tramp + 6) = (unsigned)dest - ((unsigned)codeloc + 10);
538
539 closure->cif = cif;
540 closure->fun = fun;
541 closure->user_data = user_data;
542
543 return FFI_OK;
544 }
545
546 void FFI_HIDDEN ffi_go_closure_EAX(void);
547 void FFI_HIDDEN ffi_go_closure_ECX(void);
548 void FFI_HIDDEN ffi_go_closure_STDCALL(void);
549
550 ffi_status
551 ffi_prep_go_closure (ffi_go_closure* closure, ffi_cif* cif,
552 void (*fun)(ffi_cif*,void*,void**,void*))
553 {
554 void (*dest)(void);
555
556 switch (cif->abi)
557 {
558 case FFI_SYSV:
559 case FFI_MS_CDECL:
560 dest = ffi_go_closure_ECX;
561 break;
562 case FFI_THISCALL:
563 case FFI_FASTCALL:
564 dest = ffi_go_closure_EAX;
565 break;
566 case FFI_STDCALL:
567 case FFI_PASCAL:
568 dest = ffi_go_closure_STDCALL;
569 break;
570 case FFI_REGISTER:
571 default:
572 return FFI_BAD_ABI;
573 }
574
575 closure->tramp = dest;
576 closure->cif = cif;
577 closure->fun = fun;
578
579 return FFI_OK;
580 }
581
582 /* ------- Native raw API support -------------------------------- */
583
584 #if !FFI_NO_RAW_API
585
586 void FFI_HIDDEN ffi_closure_raw_SYSV(void);
587 void FFI_HIDDEN ffi_closure_raw_THISCALL(void);
588
589 ffi_status
590 ffi_prep_raw_closure_loc (ffi_raw_closure *closure,
591 ffi_cif *cif,
592 void (*fun)(ffi_cif*,void*,ffi_raw*,void*),
593 void *user_data,
594 void *codeloc)
595 {
596 char *tramp = closure->tramp;
597 void (*dest)(void);
598 int i;
599
600 /* We currently don't support certain kinds of arguments for raw
601 closures. This should be implemented by a separate assembly
602 language routine, since it would require argument processing,
603 something we don't do now for performance. */
604 for (i = cif->nargs-1; i >= 0; i--)
605 switch (cif->arg_types[i]->type)
606 {
607 case FFI_TYPE_STRUCT:
608 case FFI_TYPE_LONGDOUBLE:
609 return FFI_BAD_TYPEDEF;
610 }
611
612 switch (cif->abi)
613 {
614 case FFI_THISCALL:
615 dest = ffi_closure_raw_THISCALL;
616 break;
617 case FFI_SYSV:
618 dest = ffi_closure_raw_SYSV;
619 break;
620 default:
621 return FFI_BAD_ABI;
622 }
623
624 /* movl imm, %eax. */
625 tramp[0] = 0xb8;
626 *(void **)(tramp + 1) = codeloc;
627
628 /* jmp dest */
629 tramp[5] = 0xe9;
630 *(unsigned *)(tramp + 6) = (unsigned)dest - ((unsigned)codeloc + 10);
631
632 closure->cif = cif;
633 closure->fun = fun;
634 closure->user_data = user_data;
635
636 return FFI_OK;
637 }
638
639 void
640 ffi_raw_call(ffi_cif *cif, void (*fn)(void), void *rvalue, ffi_raw *avalue)
641 {
642 size_t rsize, bytes;
643 struct call_frame *frame;
644 char *stack, *argp;
645 ffi_type **arg_types;
646 int flags, cabi, i, n, narg_reg;
647 const struct abi_params *pabi;
648
649 flags = cif->flags;
650 cabi = cif->abi;
651 pabi = &abi_params[cabi];
652
653 rsize = 0;
654 if (rvalue == NULL)
655 {
656 switch (flags)
657 {
658 case X86_RET_FLOAT:
659 case X86_RET_DOUBLE:
660 case X86_RET_LDOUBLE:
661 case X86_RET_STRUCTPOP:
662 case X86_RET_STRUCTARG:
663 /* The float cases need to pop the 387 stack.
664 The struct cases need to pass a valid pointer to the callee. */
665 rsize = cif->rtype->size;
666 break;
667 default:
668 /* We can pretend that the callee returns nothing. */
669 flags = X86_RET_VOID;
670 break;
671 }
672 }
673
674 bytes = cif->bytes;
675 argp = stack = alloca(bytes + sizeof(*frame) + rsize);
676 frame = (struct call_frame *)(stack + bytes);
677 if (rsize)
678 rvalue = frame + 1;
679
680 frame->fn = fn;
681 frame->flags = flags;
682 frame->rvalue = rvalue;
683
684 narg_reg = 0;
685 switch (flags)
686 {
687 case X86_RET_STRUCTARG:
688 /* The pointer is passed as the first argument. */
689 if (pabi->nregs > 0)
690 {
691 frame->regs[pabi->regs[0]] = (unsigned)rvalue;
692 narg_reg = 1;
693 break;
694 }
695 /* fallthru */
696 case X86_RET_STRUCTPOP:
697 *(void **)argp = rvalue;
698 argp += sizeof(void *);
699 bytes -= sizeof(void *);
700 break;
701 }
702
703 arg_types = cif->arg_types;
704 for (i = 0, n = cif->nargs; narg_reg < pabi->nregs && i < n; i++)
705 {
706 ffi_type *ty = arg_types[i];
707 size_t z = ty->size;
708 int t = ty->type;
709
710 if (z <= FFI_SIZEOF_ARG && t != FFI_TYPE_STRUCT && t != FFI_TYPE_FLOAT)
711 {
712 ffi_arg val = extend_basic_type (avalue, t);
713 frame->regs[pabi->regs[narg_reg++]] = val;
714 z = FFI_SIZEOF_ARG;
715 }
716 else
717 {
718 memcpy (argp, avalue, z);
719 z = ALIGN (z, FFI_SIZEOF_ARG);
720 argp += z;
721 }
722 avalue += z;
723 bytes -= z;
724 }
725 if (i < n)
726 memcpy (argp, avalue, bytes);
727
728 ffi_call_i386 (frame, stack);
729 }
730 #endif /* !FFI_NO_RAW_API */
731 #endif /* !__x86_64__ */
732
733
734 #endif
OLDNEW
« no previous file with comments | « Modules/_ctypes/libffi_ios/x86/ffi64_x86_64.c ('k') | Modules/_ctypes/libffi_ios/x86/internal64.h » ('j') | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+