Skip to content

Instantly share code, notes, and snippets.

@thomcc
Created Mar 15, 2014
Embed
What would you like to do?
A small script I use to quickly see the assembly or LLVM IR output by clang for a given piece of C++ code. (public domain, no warantee, etc.)
#!/usr/bin/env ruby
require 'optparse'
require 'tempfile'
TEMPLATE = <<-CODE_EOF
#include <cinttypes>
#include <cmath>
#include <cstddef>
#include <cstring>
#include <climits>
#include <x86intrin.h>
#define CLANG_VECTOR(Name, T, N) T Name __attribute__((ext_vector_type(N)))
#define GCC_VECTOR(Name, T, N) T Name __attribute__((__vector_size__((N)*sizeof(T))))
#define INLINE __attribute__((always_inline)) inline
#define READ_NONE __attribute__((const))
#define READ_ONLY __attribute__((pure))
#define NEVER_THROWS __attribute__((nothrow))
#define NORETURN __attribute__((noreturn))
#define PACKED __attribute__((packed))
#define ALIGNED(X) __attribute__((aligned(X)))
#define UNREACHABLE() __builtin_unreachable()
#define LIKELY(X) __builtin_expect(!!(X), 1)
#define UNLIKELY(X) __builtin_expect(!!(X), 0)
extern "C" {
bool equ(uint64_t a, uint64_t b) {
return a == b;
}
}
CODE_EOF
class AsmCC
attr_accessor :warnings, :includes
attr_accessor :opt_level, :accurate_fp
attr_accessor :enable_exns, :enable_rtti
attr_accessor :arch, :force_bits
attr_accessor :edit_asm, :show_encoding
attr_accessor :emit_llvm
attr_accessor :xflags
attr_accessor :debug
attr_accessor :template
def initialize template=TEMPLATE
@parsed_settings = false
@template = template
@warnings = %W[all effc++]
@includes = %W[#{Dir.pwd} #{Dir.pwd}/include /usr/local/include]
@opt_level = '3'
@enable_exns = false
@enable_rtti = false
@accurate_fp = false
@arch = 'native'
@xflags = []
@force_bits = nil
@edit_asm = false
@show_encoding = false
@debug = false
@emit_llvm = false
end
def parse_settings!
return self if @parsed_settings
@parsed_settings = true
OptionParser.new do |opts|
opts.on('-g', '--debug-info', 'Include debugging info?') { @debug = true }
opts.on('-O', '--opt-level [0123s]', '01234s'.split(''), 'Set optimization level)') {|v| @opt_level = v}
opts.on('-L', '--emit-llvm', 'Emit LLVM IR. (disables --show-encoding).') {|v| @emit_llvm = true}
opts.on('-M', '--accurate-math', 'Use accurate math? (off by default)') {|b| @accurate_fp = b }
opts.on('--arch ARCH', String, 'specify -march flag value (default is "native")') {|arch| @arch = arch }
opts.on('--m32', 'Force 32 bit compilation') { @force_bits = 32 }
opts.on('--m64', 'Force 64 bit compilation') { @force_bits = 64 }
opts.on('-X', '--Xc++ f,l,a,g,s', Array, 'Pass extra flags to c++ compiler') {|fs| @xflags += fs}
opts.on('-v', '--verbose', 'compile with verbose output') { @xflags << '-v'}
opts.on('-e', '--[no-]exceptions', 'Enable exceptions') {|e| @enable_exns = e}
opts.on('-r', '--[no-]rtti', 'Enable runtime type info') {|r| @enable_rtti = r}
opts.on('-S', '--edit-asm', 'show assembly and c file in editor') { @edit_asm = true }
opts.on('-W', '--warn w0,w1,etc', Array, 'Use warnings (defaults to Wall,Wextra)') {|ws| @warnings = ws }
opts.on('-I', '--include d,i,r,s', Array, 'Extra include directories.') {|is| @includes += is }
opts.on('-E', '--show-encoding', 'Show encoding?') { @show_encoding = true }
opts.on('-T', '--template FILE', String, 'Use FILE for template') do |f|
@template = IO.read(f) rescue abort("Unable to open file: #{f}")
end
end.parse!
if @emit_llvm
@show_encoding = false
end
self
end
def try_again? prompt, default
print "#{prompt} #{default ? '[Y/n]' : '[y/N]'}:"
result = gets.strip
if result.empty?
default
elsif result[0] == 'Y' || result[0] == 'y'
true
elsif result[0] == 'N' || result[0] == 'n'
false
else
default
end
end
def edit_file fpath
system(ENV['VISUAL'] || ENV['EDITOR'] || '/usr/bin/nano', fpath)
end
def is_darwin?
`uname`.strip == 'Darwin'
end
def stdlib_flag
is_darwin? ? '-stdlib=libc++' : '-stdlib=libstdc++'
end
def flags
%W[
-march=#{@arch}
#{@force_bits.nil? ? '' : '-m'+@force_bits}
-S
#{@emit_llvm ? '-emit-llvm': ''}
-Xclang -masm-verbose
-fomit-frame-pointer
-xc++
-std=gnu++1y
#{stdlib_flag}
-O#{@opt_level.strip}
#{@enable_exns ? '-fexceptions' : '-fno-exceptions'}
#{@enable_rtti ? '-frtti' : '-fno-rtti'}
#{@accurate_fp ? '' : '-ffast-math'}
#{@warnings.map{|s| to_warning s}.join ' '}
#{@includes.map{|s| to_include s}.join ' '}
-DNDEBUG
-D_GNU_SOURCE
#{@debug ? '-g' : ''}
#{@xflags.join ' '}
].reject(&:empty?)
end
def flag_str
flags.join ' '
end
def to_warning(s)
s.strip.sub /^(-?W)?/, "-W"
end
def to_include(s)
s.strip.sub /^(-?I)?/, "-I"
end
def flags_summary
flags.reject{|f| /^-[WID]/ =~ f}.join ' '
end
def invoke_compiler path
if @show_encoding
`clang #{flag_str} #{path} -o '-' | clang -cc1as -show-encoding`
else
`clang #{flag_str} #{path} -o '-'`
end
end
def compiled_with
if @show_encoding
"Compiled with `clang #{flags_summary} -o '-' | clang -cc1as -show-encoding`"
else
"Compiled with `clang #{flags_summary}`"
end
end
def run
parse_settings!
file = Tempfile.new(['asmcc', '.cc'])
path = file.path
file.print @template
file.close()
compiled = nil
success = false
while true
edit_file path
compiled = invoke_compiler path
break if success = $?.exitstatus == 0
exit(1) if not try_again?("Compilation failed, try again?", true)
end
if @edit_asm
open(path, 'a') do |f|
f.print "\n\n// ###### Generated code:\n"
f.print "// #{compiled_with}\n\n"
f.print compiled.each_line.to_a.map{|line| "// #{line.gsub(/([^\t]*)(\t)/) { $1 + " " * (8 - $1.length % 8) }}"}.join
f.print "\n"
end
edit_file path
else
puts "#{compiled_with}:\n\n"
puts compiled.each_line.to_a.map{|line| "#{line.gsub(/([^\t]*)(\t)/) { $1 + " " * (8 - $1.length % 8) }}"}.join
end
end
end
AsmCC.new.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment