| #!/usr/bin/env perl |
| |
| package x86unix; # GAS actually... |
| |
| *out=\@::out; |
| |
| $label="L000"; |
| |
| $align=($::aout)?"4":"16"; |
| $under=($::aout or $::coff)?"_":""; |
| $dot=($::aout)?"":"."; |
| $com_start="#" if ($::aout or $::coff); |
| |
| sub opsize() |
| { my $reg=shift; |
| if ($reg =~ m/^%e/o) { "l"; } |
| elsif ($reg =~ m/^%[a-d][hl]$/o) { "b"; } |
| elsif ($reg =~ m/^%[xm]/o) { undef; } |
| else { "w"; } |
| } |
| |
| # swap arguments; |
| # expand opcode with size suffix; |
| # prefix numeric constants with $; |
| sub ::generic |
| { my($opcode,$dst,$src)=@_; |
| my($tmp,$suffix,@arg); |
| |
| if (defined($src)) |
| { $src =~ s/^(e?[a-dsixphl]{2})$/%$1/o; |
| $src =~ s/^(x?mm[0-7])$/%$1/o; |
| $src =~ s/^(\-?[0-9]+)$/\$$1/o; |
| $src =~ s/^(\-?0x[0-9a-f]+)$/\$$1/o; |
| push(@arg,$src); |
| } |
| if (defined($dst)) |
| { $dst =~ s/^(\*?)(e?[a-dsixphl]{2})$/$1%$2/o; |
| $dst =~ s/^(x?mm[0-7])$/%$1/o; |
| $dst =~ s/^(\-?[0-9]+)$/\$$1/o if(!defined($src)); |
| $dst =~ s/^(\-?0x[0-9a-f]+)$/\$$1/o if(!defined($src)); |
| push(@arg,$dst); |
| } |
| |
| if ($dst =~ m/^%/o) { $suffix=&opsize($dst); } |
| elsif ($src =~ m/^%/o) { $suffix=&opsize($src); } |
| else { $suffix="l"; } |
| undef $suffix if ($dst =~ m/^%[xm]/o || $src =~ m/^%[xm]/o); |
| |
| if ($#_==0) { &::emit($opcode); } |
| elsif ($opcode =~ m/^j/o && $#_==1) { &::emit($opcode,@arg); } |
| elsif ($opcode eq "call" && $#_==1) { &::emit($opcode,@arg); } |
| elsif ($opcode =~ m/^set/&& $#_==1) { &::emit($opcode,@arg); } |
| else { &::emit($opcode.$suffix,@arg);} |
| |
| 1; |
| } |
| # |
| # opcodes not covered by ::generic above, mostly inconsistent namings... |
| # |
| sub ::movz { &::movzb(@_); } |
| sub ::pushf { &::pushfl; } |
| sub ::popf { &::popfl; } |
| sub ::cpuid { &::emit(".byte\t0x0f,0xa2"); } |
| sub ::rdtsc { &::emit(".byte\t0x0f,0x31"); } |
| |
| sub ::call { &::emit("call",(&islabel($_[0]) or "$under$_[0]")); } |
| sub ::call_ptr { &::generic("call","*$_[0]"); } |
| sub ::jmp_ptr { &::generic("jmp","*$_[0]"); } |
| |
| *::bswap = sub { &::emit("bswap","%$_[0]"); } if (!$::i386); |
| |
| # chosen SSE instructions |
| sub ::movq |
| { my($p1,$p2,$optimize)=@_; |
| if ($optimize && $p1=~/^mm[0-7]$/ && $p2=~/^mm[0-7]$/) |
| # movq between mmx registers can sink Intel CPUs |
| { &::pshufw($p1,$p2,0xe4); } |
| else |
| { &::generic("movq",@_); } |
| } |
| sub ::pshufw |
| { my($dst,$src,$magic)=@_; |
| &::emit("pshufw","\$$magic","%$src","%$dst"); |
| } |
| |
| sub ::DWP |
| { my($addr,$reg1,$reg2,$idx)=@_; |
| my $ret=""; |
| |
| $addr =~ s/^\s+//; |
| # prepend global references with optional underscore |
| $addr =~ s/^([^\+\-0-9][^\+\-]*)/islabel($1) or "$under$1"/ige; |
| |
| $reg1 = "%$reg1" if ($reg1); |
| $reg2 = "%$reg2" if ($reg2); |
| |
| $ret .= $addr if (($addr ne "") && ($addr ne 0)); |
| |
| if ($reg2) |
| { $idx!= 0 or $idx=1; |
| $ret .= "($reg1,$reg2,$idx)"; |
| } |
| elsif ($reg1) |
| { $ret .= "($reg1)"; } |
| |
| $ret; |
| } |
| sub ::QWP { &::DWP(@_); } |
| sub ::BP { &::DWP(@_); } |
| sub ::BC { @_; } |
| sub ::DWC { @_; } |
| |
| sub ::file |
| { push(@out,".file\t\"$_[0].s\"\n"); } |
| |
| sub ::function_begin_B |
| { my($func,$extra)=@_; |
| my $tmp; |
| |
| &::external_label($func); |
| $func=$under.$func; |
| |
| push(@out,".text\n.globl\t$func\n"); |
| if ($::coff) |
| { push(@out,".def\t$func;\t.scl\t2;\t.type\t32;\t.endef\n"); } |
| elsif ($::aout and !$::pic) |
| { } |
| else |
| { push(@out,".type $func,\@function\n"); } |
| push(@out,".align\t$align\n"); |
| push(@out,"$func:\n"); |
| $::stack=4; |
| } |
| |
| sub ::function_end_B |
| { my($func)=@_; |
| |
| $func=$under.$func; |
| push(@out,"${dot}L_${func}_end:\n"); |
| if ($::elf) |
| { push(@out,".size\t$func,${dot}L_${func}_end-$func\n"); } |
| $::stack=0; |
| %label=(); |
| } |
| |
| sub ::comment |
| { |
| if (!defined($com_start) or $::elf) |
| { # Regarding $::elf above... |
| # GNU and SVR4 as'es use different comment delimiters, |
| push(@out,"\n"); # so we just skip ELF comments... |
| return; |
| } |
| foreach (@_) |
| { |
| if (/^\s*$/) |
| { push(@out,"\n"); } |
| else |
| { push(@out,"\t$com_start $_ $com_end\n"); } |
| } |
| } |
| |
| sub islabel # see is argument is a known label |
| { my $i; |
| foreach $i (%label) { return $label{$i} if ($label{$i} eq $_[0]); } |
| undef; |
| } |
| |
| sub ::external_label { push(@labels,@_); } |
| |
| sub ::public_label |
| { $label{$_[0]}="${under}${_[0]}" if (!defined($label{$_[0]})); |
| push(@out,".globl\t$label{$_[0]}\n"); |
| } |
| |
| sub ::label |
| { if (!defined($label{$_[0]})) |
| { $label{$_[0]}="${dot}${label}${_[0]}"; $label++; } |
| $label{$_[0]}; |
| } |
| |
| sub ::set_label |
| { my $label=&::label($_[0]); |
| &::align($_[1]) if ($_[1]>1); |
| push(@out,"$label:\n"); |
| } |
| |
| sub ::file_end |
| { # try to detect if SSE2 or MMX extensions were used on ELF platform... |
| if ($::elf && grep {/\b%[x]?mm[0-7]\b|OPENSSL_ia32cap_P\b/i} @out) { |
| |
| push (@out,"\n.section\t.bss\n"); |
| push (@out,".comm\t${under}OPENSSL_ia32cap_P,4,4\n"); |
| |
| return; # below is not needed in OpenSSL context |
| |
| push (@out,".section\t.init\n"); |
| &::picmeup("edx","OPENSSL_ia32cap_P"); |
| # $1<<10 sets a reserved bit to signal that variable |
| # was initialized already... |
| my $code=<<___; |
| cmpl \$0,(%edx) |
| jne 3f |
| movl \$1<<10,(%edx) |
| pushf |
| popl %eax |
| movl %eax,%ecx |
| xorl \$1<<21,%eax |
| pushl %eax |
| popf |
| pushf |
| popl %eax |
| xorl %ecx,%eax |
| btl \$21,%eax |
| jnc 3f |
| pushl %ebp |
| pushl %edi |
| pushl %ebx |
| movl %edx,%edi |
| xor %eax,%eax |
| .byte 0x0f,0xa2 |
| xorl %eax,%eax |
| cmpl $1970169159,%ebx |
| setne %al |
| movl %eax,%ebp |
| cmpl $1231384169,%edx |
| setne %al |
| orl %eax,%ebp |
| cmpl $1818588270,%ecx |
| setne %al |
| orl %eax,%ebp |
| movl $1,%eax |
| .byte 0x0f,0xa2 |
| cmpl $0,%ebp |
| jne 1f |
| andb $15,%ah |
| cmpb $15,%ah |
| jne 1f |
| orl $1048576,%edx |
| 1: btl $28,%edx |
| jnc 2f |
| shrl $16,%ebx |
| cmpb $1,%bl |
| ja 2f |
| andl $4026531839,%edx |
| 2: orl \$1<<10,%edx |
| movl %edx,0(%edi) |
| popl %ebx |
| popl %edi |
| popl %ebp |
| jmp 3f |
| .align $align |
| 3: |
| ___ |
| push (@out,$code); |
| } |
| } |
| |
| sub ::data_byte { push(@out,".byte\t".join(',',@_)."\n"); } |
| sub ::data_word { push(@out,".long\t".join(',',@_)."\n"); } |
| |
| sub ::align |
| { my $val=$_[0],$p2,$i; |
| if ($::aout) |
| { for ($p2=0;$val!=0;$val>>=1) { $p2++; } |
| $val=$p2-1; |
| $val.=",0x90"; |
| } |
| push(@out,".align\t$val\n"); |
| } |
| |
| sub ::picmeup |
| { my($dst,$sym,$base,$reflabel)=@_; |
| |
| if ($::pic && ($::elf || $::aout)) |
| { if (!defined($base)) |
| { &::call(&::label("PIC_me_up")); |
| &::set_label("PIC_me_up"); |
| &::blindpop($dst); |
| &::add($dst,"\$${under}_GLOBAL_OFFSET_TABLE_+[.-". |
| &::label("PIC_me_up") . "]"); |
| } |
| else |
| { &::lea($dst,&::DWP("${under}_GLOBAL_OFFSET_TABLE_+[.-$reflabel]", |
| $base)); |
| } |
| &::mov($dst,&::DWP($under.$sym."\@GOT",$dst)); |
| } |
| else |
| { &::lea($dst,&::DWP($sym)); } |
| } |
| |
| sub ::initseg |
| { my($f)=@_; |
| my($tmp,$ctor); |
| |
| if ($::elf) |
| { $tmp=<<___; |
| .section .init |
| call $under$f |
| jmp .Linitalign |
| .align $align |
| .Linitalign: |
| ___ |
| } |
| elsif ($::coff) |
| { $tmp=<<___; # applies to both Cygwin and Mingw |
| .section .ctors |
| .long $under$f |
| ___ |
| } |
| elsif ($::aout) |
| { $ctor="${under}_GLOBAL_\$I\$$f"; |
| $tmp=".text\n"; |
| $tmp.=".type $ctor,\@function\n" if ($::pic); |
| $tmp.=<<___; # OpenBSD way... |
| .globl $ctor |
| .align 2 |
| $ctor: |
| jmp $under$f |
| ___ |
| } |
| push(@out,$tmp) if ($tmp); |
| } |
| |
| 1; |