 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
PurL Guest
|
Posted: Mon May 02, 2005 1:19 pm Post subject: Evaluation boucle for |
|
|
boujour,
Dans une boucle for comme celle ci :
for (int i = 0; i < 5 + toto; i++)
{
....
}
est-ce que 5+toto est évalué à chaque boucle, ou le compilateur est
assez malin pour faire l'évaluation au debut et stocker le resultat dans
une variable temporaire ?
Merci,
PurL
|
|
| Back to top |
|
 |
Jean-Marc Bourguet Guest
|
Posted: Mon May 02, 2005 1:29 pm Post subject: Re: Evaluation boucle for |
|
|
PurL <purl (AT) 9entoutelettre (DOT) fr> writes:
| Quote: | boujour,
Dans une boucle for comme celle ci :
for (int i = 0; i < 5 + toto; i++)
{
...
}
est-ce que 5+toto est évalué à chaque boucle, ou le compilateur est
assez malin pour faire l'évaluation au debut et stocker le resultat
dans une variable temporaire ?
|
Le comportement doit etre comme si l'expression etait evaluee a chaque
fois. Mais l'optimisation qui consiste a sortir des expressions
constantes d'une boucle est un grand classique. Reste a t'assurer que
l'optimiseur peut detecter que l'expression est constante.
Si tu veux etre sur pour un cas particulier: mesure ou regarde
l'assembleur.
A+
--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
|
|
| Back to top |
|
 |
Samuel Krempp Guest
|
Posted: Mon May 02, 2005 2:01 pm Post subject: Re: Evaluation boucle for |
|
|
[email]jm (AT) bourguet (DOT) org[/email] (02 May 2005 15:29, <pxbmzrd3gdo.fsf (AT) news (DOT) bourguet.org>) a
écrit :
| Quote: | PurL <purl (AT) 9entoutelettre (DOT) fr> writes:
boujour,
Dans une boucle for comme celle ci :
for (int i = 0; i < 5 + toto; i++)
Le comportement doit etre comme si l'expression etait evaluee a chaque
fois. Mais l'optimisation qui consiste a sortir des expressions
constantes d'une boucle est un grand classique. Reste a t'assurer que
l'optimiseur peut detecter que l'expression est constante.
|
est-ce que le compilateur doit se soucier d'éventuelles modifications de
toto par d'éventuels autres threads ? comme la norme ne connait rien des
threads, je considère que le compilo a le droit d'analyser le code dans le
modèle 'standard' de flot d'éxécution (mono-thread). Mais dans la
pratique ?
Si on imagine le fichier suivant :
int toto;
void modifToto(int n) {
toto = n;
}
void foo() {
for (int i = 0; i < 500000/toto; i++) {
cout << i << endl;
}
est-ce que les compilos usuels évalueraient 500000/toto une seule fois, ou
pas (et alors, c'est en prévision de possibles threads, ou pour d'autres
raisons ?)
--
Sam
|
|
| Back to top |
|
 |
Pierre Maurette Guest
|
Posted: Mon May 02, 2005 2:11 pm Post subject: Re: Evaluation boucle for |
|
|
PurL, le 02/05/2005, a écrit :
| Quote: | boujour,
Dans une boucle for comme celle ci :
for (int i = 0; i < 5 + toto; i++)
{
...
}
est-ce que 5+toto est évalué à chaque boucle, ou le compilateur est assez
malin pour faire l'évaluation au debut et stocker le resultat dans une
variable temporaire ?
Un bon compilateur devrait toujours le faire dans les cas exigés par le |
bon sens. Ce qui peut exclure, sans être exhaustif:
- toto est déclarée dans le même bloc que le for, mais volatile.
- toto est externe à ce bloc (disons globale ou externe), et des
fonctions sont appelées dans le bloc for. Là, ça peut dépendre de la
qualité de l'optimiseur. Avec avantage à une optimisation globale qui
prend en compte le lieur.
- Dans le même genre, &toto a été passé à une fonction qui attend un
int* non constant, idem pour une reference.
- toto ne devrait pas pouvoir être être modifiée dans le for, mais
celui-ci contient un __asm. Tous les compilos n'étudient pas le
comportement sous ce mot clé (ou _asm ou asm).
J'en oublie sans doute par ignorance du C++ ...
--
Pierre
|
|
| Back to top |
|
 |
Jean-Marc Bourguet Guest
|
Posted: Mon May 02, 2005 7:18 pm Post subject: Re: Evaluation boucle for |
|
|
Samuel Krempp <krempp (AT) crans (DOT) truc.en.trop.ens-cachan.fr> writes:
| Quote: | Si on imagine le fichier suivant :
int toto;
void modifToto(int n) {
toto = n;
}
void foo() {
for (int i = 0; i < 500000/toto; i++) {
cout << i << endl;
}
est-ce que les compilos usuels évalueraient 500000/toto
une seule fois, ou pas (et alors, c'est en prévision de
possibles threads, ou pour d'autres raisons ?)
|
J'espère qu'ils l'évalueraient à chaque fois. Quelqu'un
peut avoir fait un cout.rbuf(mybuffer) avec streambuf qui
modifie toto. En gros je m'attends que dès qu'il y a appel
à une fonction qui n'est pas définie localement, la plupart
des compilateurs supposent qu'elle peut modifier toto (il y
en a quelques uns qui sont capables de faire de l'analyse
inter-fichier si on leur demande et donc vont pousser plus
loin dans ces cas).
Ils peuvent aussi constater que garder 500000/toto à travers
les appels aux operateurs << est plus coûteux que de
réévaluer l'expression.
Il n'y a pas pour le moment de modèle mémoire standard pour
le multi-thread mais il y en a un en préparation. Je doute
qu'il demande de considérer que toto puisse être modifié
sans marquage spécial (du genre toto marqué comme volatile,
accès à une autre variable volatile).
A+
--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
|
|
| Back to top |
|
 |
adebaene@club-internet.fr Guest
|
Posted: Tue May 03, 2005 1:44 pm Post subject: Re: Evaluation boucle for |
|
|
Samuel Krempp wrote:
| Quote: | jm (AT) bourguet (DOT) org (02 May 2005 15:29,
[email]pxbmzrd3gdo.fsf (AT) news (DOT) bourguet.org[/email]>) a
écrit :
PurL <purl (AT) 9entoutelettre (DOT) fr> writes:
boujour,
Dans une boucle for comme celle ci :
for (int i = 0; i < 5 + toto; i++)
Le comportement doit etre comme si l'expression etait evaluee a
chaque
fois. Mais l'optimisation qui consiste a sortir des expressions
constantes d'une boucle est un grand classique. Reste a t'assurer
que
l'optimiseur peut detecter que l'expression est constante.
est-ce que le compilateur doit se soucier d'éventuelles
modifications de
toto par d'éventuels autres threads ? comme la norme ne connait rien
des
threads, je considère que le compilo a le droit d'analyser le code
dans le
modèle 'standard' de flot d'éxécution (mono-thread). Mais dans la
pratique ?
|
Si toto est accédée par plusieurs threads, il y a de fortes chances
que le programme est un comportement indéfini (à moins que l'accès
à toto soit garanti atomique en lecture), donc la question ne se pose
pas vraiment...
Arnaud
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Tue May 03, 2005 4:41 pm Post subject: Re: Evaluation boucle for |
|
|
Samuel Krempp wrote:
| Quote: | jm (AT) bourguet (DOT) org (02 May 2005 15:29,
[email]pxbmzrd3gdo.fsf (AT) news (DOT) bourguet.org[/email]>) a écrit :
PurL <purl (AT) 9entoutelettre (DOT) fr> writes:
Dans une boucle for comme celle ci :
for (int i = 0; i < 5 + toto; i++)
Le comportement doit etre comme si l'expression etait
evaluee a chaque fois. Mais l'optimisation qui consiste a
sortir des expressions constantes d'une boucle est un grand
classique. Reste a t'assurer que l'optimiseur peut detecter
que l'expression est constante.
est-ce que le compilateur doit se soucier d'éventuelles
modifications de toto par d'éventuels autres threads ? comme
la norme ne connait rien des threads, je considère que le
compilo a le droit d'analyser le code dans le modèle
'standard' de flot d'éxécution (mono-thread). Mais dans la
pratique ?
|
Selon la norme C++, comme tu dis, dès qu'il y a des threads, tu
as un comportement indéfini. Il faut donc chercher ailleurs.
Selon Posix, si plusieurs threads peuvent accéder à une
variable, et au moins un d'entre eux risque de la modifier, il
faut que l'utilisateur s'assure la synchronisation
explicitement. Donc, dans la mésure qu'il n'y a pas de lock dans
le code, le compilateur a le droit de supposer qu'aucun autre
thread ne le modifie.
Dans la pratique, évidemment, très peu de compilateurs vont
jusqu'à analyser l'assembleur dans les fichiers objets que tu
linkes, pour voir qu'ils ne connaissent pas la variable. Si donc
la variable est visible ailleurs, ils supposent qu'elle peut
être modifiée dans n'importe quelle fonction externe. Qu'elle
s'appelle pthread_mutex_lock ou autre.
| Quote: | Si on imagine le fichier suivant :
int toto;
void modifToto(int n) {
toto = n;
}
void foo() {
for (int i = 0; i < 500000/toto; i++) {
cout << i << endl;
}
est-ce que les compilos usuels évalueraient 500000/toto une
seule fois, ou pas (et alors, c'est en prévision de possibles
threads, ou pour d'autres raisons ?)
|
Ils y sont obligé. Parce que dans le main, j'ai installé un
streambuf dans cout qui appelle modifToto chaque fois qu'on sort
un 'n'.
En général : dès que l'expression contient une variable globale,
ou une variable dont on a pris l'adresse (y compris en forme de
référence), et que la boucle contient le moindre appel à une
fonction dont le compilateur ne voit pas l'implémentation, le
compilateur est obligé à supposer que cette fonction change la
valeur de la variable.
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
|
|
| Back to top |
|
 |
Pierre Maurette Guest
|
Posted: Tue May 10, 2005 4:06 am Post subject: Re: Evaluation boucle for |
|
|
[email]kanze (AT) gabi-soft (DOT) fr[/email], le 03/05/2005, a écrit :
[...]
| Quote: | En général : dès que l'expression contient une variable globale,
ou une variable dont on a pris l'adresse (y compris en forme de
référence), et que la boucle contient le moindre appel à une
fonction dont le compilateur ne voit pas l'implémentation, le
compilateur est obligé à supposer que cette fonction change la
valeur de la variable.
http://minilien.com/?sQNnEIKr4V |
http://minilien.com/?6C5JzGmym5
--
Pierre
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Tue May 10, 2005 8:08 am Post subject: Re: Evaluation boucle for |
|
|
Pierre Maurette wrote:
| Quote: | kanze (AT) gabi-soft (DOT) fr, le 03/05/2005, a écrit :
[...]
En général : dès que l'expression contient une variable
globale, ou une variable dont on a pris l'adresse (y compris
en forme de référence), et que la boucle contient le moindre
appel à une fonction dont le compilateur ne voit pas
l'implémentation, le compilateur est obligé à supposer que
cette fonction change la valeur de la variable.
http://minilien.com/?sQNnEIKr4V
http://minilien.com/?6C5JzGmym5
|
En somme, Microsoft te donne la possibilité de dire
explicitement ce qu'il ne pourrait pas déduire (parce que
l'information se trouve dans une autre unité de compilation).
La norme C donne aussi une possibilité de declarer certains
paramètres « restrict » -- grosso modo, c'est une promesse que
l'objet désigné par un pointeur n'est pas accédé autrement que
par ce pointeur.
Je ne suis pas expert dans la matière, mais les deux solutions
me semblent surtout ajouter aux risques d'un comportement
indéfini. J'aurais tendance à les éviter sauf en cas de
nécessité.
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
|
|
| Back to top |
|
 |
Pierre Maurette Guest
|
Posted: Tue May 10, 2005 8:50 am Post subject: Re: Evaluation boucle for |
|
|
[email]kanze (AT) gabi-soft (DOT) fr[/email], le 10/05/2005, a écrit :
[...]
| Quote: | http://minilien.com/?sQNnEIKr4V
http://minilien.com/?6C5JzGmym5
En somme, Microsoft te donne la possibilité de dire
explicitement ce qu'il ne pourrait pas déduire (parce que
l'information se trouve dans une autre unité de compilation).
Je m'étais gourré dans mon minilien, il y avait également /Og, mais il |
reprend simplement /Oa /Ow.
| Quote: | La norme C donne aussi une possibilité de declarer certains
paramètres « restrict » -- grosso modo, c'est une promesse que
l'objet désigné par un pointeur n'est pas accédé autrement que
par ce pointeur.
Vous expliquez mieux que la norme C ... |
| Quote: | Je ne suis pas expert dans la matière, mais les deux solutions
me semblent surtout ajouter aux risques d'un comportement
indéfini. J'aurais tendance à les éviter sauf en cas de
nécessité.
Cette option, elle signifie en gros: "Compilo, nom ami, je te jure que |
ne fais pas l'abruti en modifiant en loucedé mes variable par des
pointeurs occultes (et des __asm ?), optimise à ta guise".
L'exemple donné n'est pas convaincant (il suffit de faire l'addition
avant la boucle). Mais dans d'autres cas, pourquoi pas ?. En #pragma,
bien sûr.
--
Pierre
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Tue May 10, 2005 9:51 am Post subject: Re: Evaluation boucle for |
|
|
Pierre Maurette wrote:
[...]
| Quote: | Je ne suis pas expert dans la matière, mais les deux
solutions me semblent surtout ajouter aux risques d'un
comportement indéfini. J'aurais tendance à les éviter sauf
en cas de nécessité.
Cette option, elle signifie en gros: "Compilo, nom ami, je te
jure que ne fais pas l'abruti en modifiant en loucedé mes
variable par des pointeurs occultes (et des __asm ?), optimise
à ta guise". L'exemple donné n'est pas convaincant (il suffit
de faire l'addition avant la boucle). Mais dans d'autres cas,
pourquoi pas ?. En #pragma, bien sûr.
|
Si tu parles de l'exemple dans la doc Microsoft dont tu as posté
le lien, il est bien convaincant. L'expression dans la boucle,
c'est : « *p = x + y ». Et justement, si p pointe à x ou à y, il
faut que le compilateur fasse l'addition chaque fois dans la
boucle ; ce n'est que dans l'absence établie (par preuve ou par
déclaration sur l'honneur du programmeur) de cette possibilité
que le compilateur peut enlever l'addition de la boucle.
Au fur et à mésure que le parallelisme des processeurs augmente,
le problème devient plus aigu. Considérons la boucle suivante :
for ( int i = 0 ; i < 1000000 ; ++ i ) {
a[ i ] = b[ i ] + c[ i ] ;
}
Sur un processeur hyperthreadé, un compilateur Java ou Ada
pourrait splitter la boucle en deux, en l'exécutant pour les
indices paires dans un thread, et pour les indices impaires dans
l'autre. Si la boucle est dans une fonction, et a, b et c sont
en fait des pointeurs, un compilateur C ou C++ ne peut pas le
faire, parce qu'on aurait pu appeler la fonction avec quelque
chose comme (tab + 1, tab, tab).
Note bien que ni restrict, ni les options Microsoft n'aide ici,
parce qu'ils interdisent aussi l'appel (tab, tab, tab), qu'on
veut permettre.
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
|
|
| Back to top |
|
 |
Pierre Maurette Guest
|
Posted: Tue May 10, 2005 11:28 am Post subject: Re: Evaluation boucle for |
|
|
[email]kanze (AT) gabi-soft (DOT) fr[/email], le 10/05/2005, a écrit :
| Quote: | Pierre Maurette wrote:
[...]
Cette option, elle signifie en gros: "Compilo, nom ami, je te
jure que ne fais pas l'abruti en modifiant en loucedé mes
variable par des pointeurs occultes (et des __asm ?), optimise
à ta guise". L'exemple donné n'est pas convaincant (il suffit
de faire l'addition avant la boucle). Mais dans d'autres cas,
pourquoi pas ?. En #pragma, bien sûr.
Si tu parles de l'exemple dans la doc Microsoft dont tu as posté
le lien, il est bien convaincant. L'expression dans la boucle,
c'est : « *p = x + y ». Et justement, si p pointe à x ou à y, il
faut que le compilateur fasse l'addition chaque fois dans la
boucle ; ce n'est que dans l'absence établie (par preuve ou par
déclaration sur l'honneur du programmeur) de cette possibilité
que le compilateur peut enlever l'addition de la boucle.
Nous sommes d'accord. L'exemple est explicite *du danger*. |
Je voulais dire qu'il ne l'est pas de l'utilité de /Oa. Si l'on est sûr
de soi, on pourra toujours faire, *ici*:
i = -100;
const type t = x + y; // C++ et C99
t = x + y; /* Cpré99 */
while( i < 0 )
{
i += t;
*p = i;
}
| Quote: | Au fur et à mésure que le parallelisme des processeurs augmente,
le problème devient plus aigu. Considérons la boucle suivante :
for ( int i = 0 ; i < 1000000 ; ++ i ) {
a[ i ] = b[ i ] + c[ i ] ;
}
Sur un processeur hyperthreadé, un compilateur Java ou Ada
pourrait splitter la boucle en deux, en l'exécutant pour les
indices paires dans un thread, et pour les indices impaires dans
l'autre. Si la boucle est dans une fonction, et a, b et c sont
en fait des pointeurs, un compilateur C ou C++ ne peut pas le
faire, parce qu'on aurait pu appeler la fonction avec quelque
chose comme (tab + 1, tab, tab).
Note bien que ni restrict, ni les options Microsoft n'aide ici,
parce qu'ils interdisent aussi l'appel (tab, tab, tab), qu'on
veut permettre.
Chez Microsoft, restera plus que les intrinsics sur 128 bits /16 ou 32 |
en parallèle, ou un coup de __asm selon les versions
On pourra faire (tab, tab, tab) mais on aura des contraintes
d'alignement.
--
Pierre
|
|
| Back to top |
|
 |
Samuel Krempp Guest
|
Posted: Wed May 11, 2005 9:54 am Post subject: Re: Evaluation boucle for |
|
|
[email]kanze (AT) gabi-soft (DOT) fr[/email] (10 May 2005 11:51,
<1115718664.695382.58750 (AT) f14g2000cwb (DOT) googlegroups.com>) a écrit :
| Quote: | for ( int i = 0 ; i < 1000000 ; ++ i ) {
a[ i ] = b[ i ] + c[ i ] ;
}
Sur un processeur hyperthreadé, un compilateur Java ou Ada
pourrait splitter la boucle en deux, en l'exécutant pour les
indices paires dans un thread, et pour les indices impaires dans
l'autre. Si la boucle est dans une fonction, et a, b et c sont
en fait des pointeurs, un compilateur C ou C++ ne peut pas le
faire, parce qu'on aurait pu appeler la fonction avec quelque
chose comme (tab + 1, tab, tab).
|
le compilateur ne pourrait-il pas générer le code pour les 2 cas (suppose
aucun alias // aucune supposition), lorsque la fonction est appelée faire
un branchement selon qu'il peut utiliser le code optimisé ou non ?
ça me semble simple et guère moins efficace même lorsque l'on pourrait
utiliser un /Og
--
Sam
|
|
| Back to top |
|
 |
Pierre Maurette Guest
|
Posted: Wed May 11, 2005 10:33 am Post subject: Re: Evaluation boucle for |
|
|
Samuel Krempp, le 11/05/2005, a écrit :
[...]
| Quote: | le compilateur ne pourrait-il pas générer le code pour les 2 cas (suppose
aucun alias // aucune supposition), lorsque la fonction est appelée faire
un branchement selon qu'il peut utiliser le code optimisé ou non ?
ça me semble simple et guère moins efficace même lorsque l'on pourrait
utiliser un /Og
Ça pourrait générer des surprises lors d'un débogage, surtout crapuleux |
J'ai vu un truc comme ça sous VC7. C'était au cours de tests dont j'ai
parlé ici, voir si on pouvait par 'register' favoriser une branche que
le programmeur sait être prise presqu'à chaque fois, alors que le
compilateur ne peut pas le supposer. VC7 était le plus rapide, et
'register' ne changeait rien. Le code objet semblait bien compliqué, et
de toute évidence des blocs entiers étaient dupliqués.
Mais j'ai réfléchi plus tard qu'il n'est pas certain que le code
exécutable soit dupliqué lui aussi. Le code objet était obtenu par
/FAs, et il est possible que le bloc soit choisi à l'édition de liens
et non à l'exécution. Faudrait que je ressorte ce truc, surtout que
j'ai depuis découvert WinDbg...
Puisqu'on est là dessus, je me pose une question : j'avais pour
habitude, parce que ça m'a paru plus facile pour les makefile et
fichiers de commande, de compiler sans lier, puis de lier. Je me
demande si cette manie peut empêcher des optimisations ...
--
Pierre
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Wed May 11, 2005 4:42 pm Post subject: Re: Evaluation boucle for |
|
|
Samuel Krempp wrote:
| Quote: | kanze (AT) gabi-soft (DOT) fr (10 May 2005 11:51,
[email]1115718664.695382.58750 (AT) f14g2000cwb (DOT) googlegroups.com[/email]>) a écrit :
for ( int i = 0 ; i < 1000000 ; ++ i ) {
a[ i ] = b[ i ] + c[ i ] ;
}
Sur un processeur hyperthreadé, un compilateur Java ou Ada
pourrait splitter la boucle en deux, en l'exécutant pour les
indices paires dans un thread, et pour les indices impaires
dans l'autre. Si la boucle est dans une fonction, et a, b et
c sont en fait des pointeurs, un compilateur C ou C++ ne
peut pas le faire, parce qu'on aurait pu appeler la fonction
avec quelque chose comme (tab + 1, tab, tab).
le compilateur ne pourrait-il pas générer le code pour les 2
cas (suppose aucun alias // aucune supposition), lorsque la
fonction est appelée faire un branchement selon qu'il peut
utiliser le code optimisé ou non ?
|
Tout à fait. Je crois même que certains compilateurs le font. Au
moins si les informations en provenance du profiling indique ce
c'est dans un chemin critique, et que les recouverements sont en
fait très rare.
De même, un compilateur qui fait un analyse inter-modules
pourrait, en théorie, en tout cas, determiner qu'en fait, la
fonction ne serait jamais appelée avec des tableaux qui
s'échevauchent, et faire l'optimisation. J'ai même vaguement
entendu parler d'un tel compilateur, mais je ne sais pas dans
quelle mésure la personne qui en parlait parler de la réalité,
ou de ses souhaits.
| Quote: | ça me semble simple et guère moins efficace même lorsque l'on
pourrait utiliser un /Og
|
Tout à fait.
N'oublions pas qu'on parle ici surtout des compilateurs pour des
processeurs parallels. Il y a peu, ça signifiait des Cray et
companie. Depuis peu, en revanche, il y a du hyper-threading sur
des PC. Il va falloir que les implémenteurs de compilateur s'y
mettent.
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|