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.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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