String routinesString BuildingIterating string characters and passing them to a BIOS APIString length calculation implementation in Assembly (NASM)Check whether string ends with stringConvert string array to string with separatorAssembly 8086 program to input a 16-bit signed number (in string form) and output its binary equivalentE820 display using X86 legacy boot sector or DOS 6.22 com fileMIPS assembly string to int functionPrinting binary string in assemblystring length in x64 assembly (fasm)
Are sweatpants frowned upon on flights?
How could a self contained organic body propel itself in space
Inspiration for failed idea?
What is Soda Fountain Etiquette?
Why does `buck` mean `step-down`?
What's the point of fighting monsters in Zelda BotW?
Should I ask for a raise one month before the end of an internship?
Why can't you say don't instead of won't?
What should be done with the carbon when using magic to get oxygen from carbon dioxide?
How to investigate an unknown 1.5GB file named "sudo" in my Linux home directory?
How did medieval manors handle population growth? Was there room for more fields to be ploughed?
Normalized Malbolge to Malbolge translator
Group riding etiquette
Defending Castle from Zombies
Can a network vulnerability be exploited locally?
In Endgame, wouldn't Stark have remembered Hulk busting out of the stairwell?
Cutting numbers into a specific decimals
How can I reply to people who accuse me of putting people out of work?
What is the sound/audio equivalent of "unsightly"?
Why doesn't Starship have four landing legs?
Pen test results for web application include a file from a forbidden directory that is not even used or referenced
What does it mean to move a single flight control to its full deflection?
Looking for a plural noun related to ‘fulcrum’ or ‘pivot’ that denotes multiple things as crucial to success
What checks exist against overuse of presidential pardons in the USA?
String routines
String BuildingIterating string characters and passing them to a BIOS APIString length calculation implementation in Assembly (NASM)Check whether string ends with stringConvert string array to string with separatorAssembly 8086 program to input a 16-bit signed number (in string form) and output its binary equivalentE820 display using X86 legacy boot sector or DOS 6.22 com fileMIPS assembly string to int functionPrinting binary string in assemblystring length in x64 assembly (fasm)
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
$begingroup$
These are a few string routines (just the mem*
ones). I've tried to optimize them the best I can without having them be too big, but I'm unsure if I've done a good job.
I'd prefer size over speed unless it's just a few bytes, in which case that would be fine. I would also prefer not to sacrifice simplicity for speed.
memchr.S
(related):
.globl memchr
memchr:
mov %rdx, %rcx
movzbl %sil, %eax
repne scasb
lea -1(%rdi), %rax
test %rcx, %rcx
cmove %rcx, %rax
ret
memcmp.S
:
.globl memcmp
memcmp:
mov %rdx, %rcx
repe cmpsb
movzbl -1(%rdi), %eax
movzbl -1(%rsi), %edx
sub %edx, %eax
ret
memcpy.S
:
.globl memcpy
memcpy:
mov %rdx, %rcx
mov %rdi, %rax
rep movsb
ret
memmove.S
:
.globl memmove
memmove:
mov %rdx, %rcx
mov %rdi, %rax
cmp %rdi, %rsi
jge 0f
dec %rdx
add %rdx, %rdi
add %rdx, %rsi
std
0: rep movsb
cld
ret
memrchr.S
:
.globl memrchr
memrchr:
mov %rdx, %rcx
add %rdx, %rdi
movzbl %sil, %eax
std
repne scasb
cld
lea 1(%rdi), %rax
test %rcx, %rcx
cmove %rcx, %rax
ret
memset.S
:
.globl memset
memset:
mov %rdx, %rcx
mov %rdi, %rdx
movzbl %sil, %eax
rep stosb
mov %rdx, %rax
ret
As usual for Stack Exchange sites, this code is released under CC/by-sa 3.0, but any future changes can be accessed here.
strings assembly x86
$endgroup$
add a comment |
$begingroup$
These are a few string routines (just the mem*
ones). I've tried to optimize them the best I can without having them be too big, but I'm unsure if I've done a good job.
I'd prefer size over speed unless it's just a few bytes, in which case that would be fine. I would also prefer not to sacrifice simplicity for speed.
memchr.S
(related):
.globl memchr
memchr:
mov %rdx, %rcx
movzbl %sil, %eax
repne scasb
lea -1(%rdi), %rax
test %rcx, %rcx
cmove %rcx, %rax
ret
memcmp.S
:
.globl memcmp
memcmp:
mov %rdx, %rcx
repe cmpsb
movzbl -1(%rdi), %eax
movzbl -1(%rsi), %edx
sub %edx, %eax
ret
memcpy.S
:
.globl memcpy
memcpy:
mov %rdx, %rcx
mov %rdi, %rax
rep movsb
ret
memmove.S
:
.globl memmove
memmove:
mov %rdx, %rcx
mov %rdi, %rax
cmp %rdi, %rsi
jge 0f
dec %rdx
add %rdx, %rdi
add %rdx, %rsi
std
0: rep movsb
cld
ret
memrchr.S
:
.globl memrchr
memrchr:
mov %rdx, %rcx
add %rdx, %rdi
movzbl %sil, %eax
std
repne scasb
cld
lea 1(%rdi), %rax
test %rcx, %rcx
cmove %rcx, %rax
ret
memset.S
:
.globl memset
memset:
mov %rdx, %rcx
mov %rdi, %rdx
movzbl %sil, %eax
rep stosb
mov %rdx, %rax
ret
As usual for Stack Exchange sites, this code is released under CC/by-sa 3.0, but any future changes can be accessed here.
strings assembly x86
$endgroup$
add a comment |
$begingroup$
These are a few string routines (just the mem*
ones). I've tried to optimize them the best I can without having them be too big, but I'm unsure if I've done a good job.
I'd prefer size over speed unless it's just a few bytes, in which case that would be fine. I would also prefer not to sacrifice simplicity for speed.
memchr.S
(related):
.globl memchr
memchr:
mov %rdx, %rcx
movzbl %sil, %eax
repne scasb
lea -1(%rdi), %rax
test %rcx, %rcx
cmove %rcx, %rax
ret
memcmp.S
:
.globl memcmp
memcmp:
mov %rdx, %rcx
repe cmpsb
movzbl -1(%rdi), %eax
movzbl -1(%rsi), %edx
sub %edx, %eax
ret
memcpy.S
:
.globl memcpy
memcpy:
mov %rdx, %rcx
mov %rdi, %rax
rep movsb
ret
memmove.S
:
.globl memmove
memmove:
mov %rdx, %rcx
mov %rdi, %rax
cmp %rdi, %rsi
jge 0f
dec %rdx
add %rdx, %rdi
add %rdx, %rsi
std
0: rep movsb
cld
ret
memrchr.S
:
.globl memrchr
memrchr:
mov %rdx, %rcx
add %rdx, %rdi
movzbl %sil, %eax
std
repne scasb
cld
lea 1(%rdi), %rax
test %rcx, %rcx
cmove %rcx, %rax
ret
memset.S
:
.globl memset
memset:
mov %rdx, %rcx
mov %rdi, %rdx
movzbl %sil, %eax
rep stosb
mov %rdx, %rax
ret
As usual for Stack Exchange sites, this code is released under CC/by-sa 3.0, but any future changes can be accessed here.
strings assembly x86
$endgroup$
These are a few string routines (just the mem*
ones). I've tried to optimize them the best I can without having them be too big, but I'm unsure if I've done a good job.
I'd prefer size over speed unless it's just a few bytes, in which case that would be fine. I would also prefer not to sacrifice simplicity for speed.
memchr.S
(related):
.globl memchr
memchr:
mov %rdx, %rcx
movzbl %sil, %eax
repne scasb
lea -1(%rdi), %rax
test %rcx, %rcx
cmove %rcx, %rax
ret
memcmp.S
:
.globl memcmp
memcmp:
mov %rdx, %rcx
repe cmpsb
movzbl -1(%rdi), %eax
movzbl -1(%rsi), %edx
sub %edx, %eax
ret
memcpy.S
:
.globl memcpy
memcpy:
mov %rdx, %rcx
mov %rdi, %rax
rep movsb
ret
memmove.S
:
.globl memmove
memmove:
mov %rdx, %rcx
mov %rdi, %rax
cmp %rdi, %rsi
jge 0f
dec %rdx
add %rdx, %rdi
add %rdx, %rsi
std
0: rep movsb
cld
ret
memrchr.S
:
.globl memrchr
memrchr:
mov %rdx, %rcx
add %rdx, %rdi
movzbl %sil, %eax
std
repne scasb
cld
lea 1(%rdi), %rax
test %rcx, %rcx
cmove %rcx, %rax
ret
memset.S
:
.globl memset
memset:
mov %rdx, %rcx
mov %rdi, %rdx
movzbl %sil, %eax
rep stosb
mov %rdx, %rax
ret
As usual for Stack Exchange sites, this code is released under CC/by-sa 3.0, but any future changes can be accessed here.
strings assembly x86
strings assembly x86
asked Aug 16 at 19:38
JL2210JL2210
23010 bronze badges
23010 bronze badges
add a comment |
add a comment |
3 Answers
3
active
oldest
votes
$begingroup$
The code looks straight-forward and really optimized for size and simplicity.
There's a small detail that I would change, though: replace cmove
with cmovz
, to make the code more expressive. It's not that "being equal" would be of any interest here, it's the zeroness of %ecx
that is interesting.
I like the omitted second jmp
in memmove. It's obvious after thinking a few seconds about it.
According to this quote it's ok to rely on the direction flag being always cleared.
I still suggest to write a few unit tests to be on the safe side.
$endgroup$
$begingroup$
See my answer for a bug that I found on my own (found by writing unit tests).
$endgroup$
– JL2210
Aug 16 at 22:57
add a comment |
$begingroup$
There's a bug in your code if memchr
finds %sil
in the last byte of %rdi
; if %rcx
tests to be zero and yet the byte has been found, it will incorrectly return zero.
To fix that, do something like this:
.globl memchr
memchr:
mov %rdx, %rcx
movzbl %sil, %eax
repne scasb
sete %cl
lea -1(%rdi), %rax
test %cl, %cl
cmovz %rcx, %rax
ret
The same applies to memrchr
.
$endgroup$
add a comment |
$begingroup$
In memmove you have the following:
cmp %rdi, %rsi
jge 0f
(cmp rsi, rdi
in Intel syntax I believe.) For rsi = 8000_0000_0000_0000h and rdi = 7FFF_FFFF_FFFF_FFFFh (we want to jump to make a forward move here) the signed-comparison conditional branch "jump if greater or equal" evaluates rsi as being "less than" rdi (rsi being a negative number in 64-bit two's complement while rdi is positive), so it doesn't jump and will make a backwards move. This is incorrect. You should use the equivalent unsigned branch "jump if above or equal", jae
instead.
$endgroup$
1
$begingroup$
Isn't this only an issue when a userspace address and kernelspace address are mixed?
$endgroup$
– harold
Aug 17 at 14:26
$begingroup$
How likely is this in reality, though? The least you could expect is a segfault.
$endgroup$
– JL2210
Aug 17 at 14:27
1
$begingroup$
It may not be an issue depending on the operating system / address-space layout. However, if it does happen, then a wrong move direction (if the buffers are actually overlapping) will result in silently corrupting the destination buffer.
$endgroup$
– ecm
Aug 17 at 14:29
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f226285%2fstring-routines%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
The code looks straight-forward and really optimized for size and simplicity.
There's a small detail that I would change, though: replace cmove
with cmovz
, to make the code more expressive. It's not that "being equal" would be of any interest here, it's the zeroness of %ecx
that is interesting.
I like the omitted second jmp
in memmove. It's obvious after thinking a few seconds about it.
According to this quote it's ok to rely on the direction flag being always cleared.
I still suggest to write a few unit tests to be on the safe side.
$endgroup$
$begingroup$
See my answer for a bug that I found on my own (found by writing unit tests).
$endgroup$
– JL2210
Aug 16 at 22:57
add a comment |
$begingroup$
The code looks straight-forward and really optimized for size and simplicity.
There's a small detail that I would change, though: replace cmove
with cmovz
, to make the code more expressive. It's not that "being equal" would be of any interest here, it's the zeroness of %ecx
that is interesting.
I like the omitted second jmp
in memmove. It's obvious after thinking a few seconds about it.
According to this quote it's ok to rely on the direction flag being always cleared.
I still suggest to write a few unit tests to be on the safe side.
$endgroup$
$begingroup$
See my answer for a bug that I found on my own (found by writing unit tests).
$endgroup$
– JL2210
Aug 16 at 22:57
add a comment |
$begingroup$
The code looks straight-forward and really optimized for size and simplicity.
There's a small detail that I would change, though: replace cmove
with cmovz
, to make the code more expressive. It's not that "being equal" would be of any interest here, it's the zeroness of %ecx
that is interesting.
I like the omitted second jmp
in memmove. It's obvious after thinking a few seconds about it.
According to this quote it's ok to rely on the direction flag being always cleared.
I still suggest to write a few unit tests to be on the safe side.
$endgroup$
The code looks straight-forward and really optimized for size and simplicity.
There's a small detail that I would change, though: replace cmove
with cmovz
, to make the code more expressive. It's not that "being equal" would be of any interest here, it's the zeroness of %ecx
that is interesting.
I like the omitted second jmp
in memmove. It's obvious after thinking a few seconds about it.
According to this quote it's ok to rely on the direction flag being always cleared.
I still suggest to write a few unit tests to be on the safe side.
edited Aug 16 at 23:19
JL2210
23010 bronze badges
23010 bronze badges
answered Aug 16 at 21:15
Roland IlligRoland Illig
15k2 gold badges24 silver badges57 bronze badges
15k2 gold badges24 silver badges57 bronze badges
$begingroup$
See my answer for a bug that I found on my own (found by writing unit tests).
$endgroup$
– JL2210
Aug 16 at 22:57
add a comment |
$begingroup$
See my answer for a bug that I found on my own (found by writing unit tests).
$endgroup$
– JL2210
Aug 16 at 22:57
$begingroup$
See my answer for a bug that I found on my own (found by writing unit tests).
$endgroup$
– JL2210
Aug 16 at 22:57
$begingroup$
See my answer for a bug that I found on my own (found by writing unit tests).
$endgroup$
– JL2210
Aug 16 at 22:57
add a comment |
$begingroup$
There's a bug in your code if memchr
finds %sil
in the last byte of %rdi
; if %rcx
tests to be zero and yet the byte has been found, it will incorrectly return zero.
To fix that, do something like this:
.globl memchr
memchr:
mov %rdx, %rcx
movzbl %sil, %eax
repne scasb
sete %cl
lea -1(%rdi), %rax
test %cl, %cl
cmovz %rcx, %rax
ret
The same applies to memrchr
.
$endgroup$
add a comment |
$begingroup$
There's a bug in your code if memchr
finds %sil
in the last byte of %rdi
; if %rcx
tests to be zero and yet the byte has been found, it will incorrectly return zero.
To fix that, do something like this:
.globl memchr
memchr:
mov %rdx, %rcx
movzbl %sil, %eax
repne scasb
sete %cl
lea -1(%rdi), %rax
test %cl, %cl
cmovz %rcx, %rax
ret
The same applies to memrchr
.
$endgroup$
add a comment |
$begingroup$
There's a bug in your code if memchr
finds %sil
in the last byte of %rdi
; if %rcx
tests to be zero and yet the byte has been found, it will incorrectly return zero.
To fix that, do something like this:
.globl memchr
memchr:
mov %rdx, %rcx
movzbl %sil, %eax
repne scasb
sete %cl
lea -1(%rdi), %rax
test %cl, %cl
cmovz %rcx, %rax
ret
The same applies to memrchr
.
$endgroup$
There's a bug in your code if memchr
finds %sil
in the last byte of %rdi
; if %rcx
tests to be zero and yet the byte has been found, it will incorrectly return zero.
To fix that, do something like this:
.globl memchr
memchr:
mov %rdx, %rcx
movzbl %sil, %eax
repne scasb
sete %cl
lea -1(%rdi), %rax
test %cl, %cl
cmovz %rcx, %rax
ret
The same applies to memrchr
.
edited Aug 17 at 3:08
answered Aug 16 at 22:41
JL2210JL2210
23010 bronze badges
23010 bronze badges
add a comment |
add a comment |
$begingroup$
In memmove you have the following:
cmp %rdi, %rsi
jge 0f
(cmp rsi, rdi
in Intel syntax I believe.) For rsi = 8000_0000_0000_0000h and rdi = 7FFF_FFFF_FFFF_FFFFh (we want to jump to make a forward move here) the signed-comparison conditional branch "jump if greater or equal" evaluates rsi as being "less than" rdi (rsi being a negative number in 64-bit two's complement while rdi is positive), so it doesn't jump and will make a backwards move. This is incorrect. You should use the equivalent unsigned branch "jump if above or equal", jae
instead.
$endgroup$
1
$begingroup$
Isn't this only an issue when a userspace address and kernelspace address are mixed?
$endgroup$
– harold
Aug 17 at 14:26
$begingroup$
How likely is this in reality, though? The least you could expect is a segfault.
$endgroup$
– JL2210
Aug 17 at 14:27
1
$begingroup$
It may not be an issue depending on the operating system / address-space layout. However, if it does happen, then a wrong move direction (if the buffers are actually overlapping) will result in silently corrupting the destination buffer.
$endgroup$
– ecm
Aug 17 at 14:29
add a comment |
$begingroup$
In memmove you have the following:
cmp %rdi, %rsi
jge 0f
(cmp rsi, rdi
in Intel syntax I believe.) For rsi = 8000_0000_0000_0000h and rdi = 7FFF_FFFF_FFFF_FFFFh (we want to jump to make a forward move here) the signed-comparison conditional branch "jump if greater or equal" evaluates rsi as being "less than" rdi (rsi being a negative number in 64-bit two's complement while rdi is positive), so it doesn't jump and will make a backwards move. This is incorrect. You should use the equivalent unsigned branch "jump if above or equal", jae
instead.
$endgroup$
1
$begingroup$
Isn't this only an issue when a userspace address and kernelspace address are mixed?
$endgroup$
– harold
Aug 17 at 14:26
$begingroup$
How likely is this in reality, though? The least you could expect is a segfault.
$endgroup$
– JL2210
Aug 17 at 14:27
1
$begingroup$
It may not be an issue depending on the operating system / address-space layout. However, if it does happen, then a wrong move direction (if the buffers are actually overlapping) will result in silently corrupting the destination buffer.
$endgroup$
– ecm
Aug 17 at 14:29
add a comment |
$begingroup$
In memmove you have the following:
cmp %rdi, %rsi
jge 0f
(cmp rsi, rdi
in Intel syntax I believe.) For rsi = 8000_0000_0000_0000h and rdi = 7FFF_FFFF_FFFF_FFFFh (we want to jump to make a forward move here) the signed-comparison conditional branch "jump if greater or equal" evaluates rsi as being "less than" rdi (rsi being a negative number in 64-bit two's complement while rdi is positive), so it doesn't jump and will make a backwards move. This is incorrect. You should use the equivalent unsigned branch "jump if above or equal", jae
instead.
$endgroup$
In memmove you have the following:
cmp %rdi, %rsi
jge 0f
(cmp rsi, rdi
in Intel syntax I believe.) For rsi = 8000_0000_0000_0000h and rdi = 7FFF_FFFF_FFFF_FFFFh (we want to jump to make a forward move here) the signed-comparison conditional branch "jump if greater or equal" evaluates rsi as being "less than" rdi (rsi being a negative number in 64-bit two's complement while rdi is positive), so it doesn't jump and will make a backwards move. This is incorrect. You should use the equivalent unsigned branch "jump if above or equal", jae
instead.
answered Aug 17 at 14:17
ecmecm
1112 bronze badges
1112 bronze badges
1
$begingroup$
Isn't this only an issue when a userspace address and kernelspace address are mixed?
$endgroup$
– harold
Aug 17 at 14:26
$begingroup$
How likely is this in reality, though? The least you could expect is a segfault.
$endgroup$
– JL2210
Aug 17 at 14:27
1
$begingroup$
It may not be an issue depending on the operating system / address-space layout. However, if it does happen, then a wrong move direction (if the buffers are actually overlapping) will result in silently corrupting the destination buffer.
$endgroup$
– ecm
Aug 17 at 14:29
add a comment |
1
$begingroup$
Isn't this only an issue when a userspace address and kernelspace address are mixed?
$endgroup$
– harold
Aug 17 at 14:26
$begingroup$
How likely is this in reality, though? The least you could expect is a segfault.
$endgroup$
– JL2210
Aug 17 at 14:27
1
$begingroup$
It may not be an issue depending on the operating system / address-space layout. However, if it does happen, then a wrong move direction (if the buffers are actually overlapping) will result in silently corrupting the destination buffer.
$endgroup$
– ecm
Aug 17 at 14:29
1
1
$begingroup$
Isn't this only an issue when a userspace address and kernelspace address are mixed?
$endgroup$
– harold
Aug 17 at 14:26
$begingroup$
Isn't this only an issue when a userspace address and kernelspace address are mixed?
$endgroup$
– harold
Aug 17 at 14:26
$begingroup$
How likely is this in reality, though? The least you could expect is a segfault.
$endgroup$
– JL2210
Aug 17 at 14:27
$begingroup$
How likely is this in reality, though? The least you could expect is a segfault.
$endgroup$
– JL2210
Aug 17 at 14:27
1
1
$begingroup$
It may not be an issue depending on the operating system / address-space layout. However, if it does happen, then a wrong move direction (if the buffers are actually overlapping) will result in silently corrupting the destination buffer.
$endgroup$
– ecm
Aug 17 at 14:29
$begingroup$
It may not be an issue depending on the operating system / address-space layout. However, if it does happen, then a wrong move direction (if the buffers are actually overlapping) will result in silently corrupting the destination buffer.
$endgroup$
– ecm
Aug 17 at 14:29
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f226285%2fstring-routines%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown