Skip to content

Commit

Permalink
added support for AOT compiled source code objects in #require
Browse files Browse the repository at this point in the history
git-svn-id: http://svn.macosforge.org/repository/ruby/MacRuby/trunk@3125 23306eb0-4c56-4727-a40e-e92c0eb68959
  • Loading branch information
lrz committed Dec 16, 2009
1 parent 547292f commit b451db6
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 21 deletions.
101 changes: 82 additions & 19 deletions bin/rubyc
Expand Up @@ -15,7 +15,6 @@ class Compiler
def initialize(argv) def initialize(argv)
@mode = :normal @mode = :normal
@archs = [] @archs = []
@frameworks = []
@internal = argv.delete('--internal') @internal = argv.delete('--internal')


# Parse arguments. # Parse arguments.
Expand All @@ -24,8 +23,8 @@ class Compiler
opts.on('-c', 'Compile and assemble, but do not link') { @dont_link = true } opts.on('-c', 'Compile and assemble, but do not link') { @dont_link = true }
opts.on('-o <file>', 'Place the output into <file>') { |output| @output = output } opts.on('-o <file>', 'Place the output into <file>') { |output| @output = output }
#opts.on('--mode [MODE]', "Select compilation mode (normal or full)") { |mode| @mode = mode.intern } #opts.on('--mode [MODE]', "Select compilation mode (normal or full)") { |mode| @mode = mode.intern }
#opts.on('--framework <framework>', 'Link against <framework>') { |path| @frameworks << path } opts.on('--static', "Create a standalone static executable") { @static = true }
opts.on('--static', "Create a standalone static executable") { @static = true } opts.on('--dylib', "Create a dynamic library") { @dylib = true }
opts.on('-C', 'Compile, assemble and link a loadable object file') { @bundle = true } opts.on('-C', 'Compile, assemble and link a loadable object file') { @bundle = true }
opts.on('-a', '--arch <ARCH>', 'Compile for specified CPU architecture') { |arch| @archs << arch } opts.on('-a', '--arch <ARCH>', 'Compile for specified CPU architecture') { |arch| @archs << arch }
opts.on('-v', '--version', 'Display the version') { puts RUBY_DESCRIPTION; exit 1 } opts.on('-v', '--version', 'Display the version') { puts RUBY_DESCRIPTION; exit 1 }
Expand Down Expand Up @@ -74,6 +73,7 @@ class Compiler
end end
if @dont_link or @bundle if @dont_link or @bundle
die "Cannot specify --static when not building an executable" if @static die "Cannot specify --static when not building an executable" if @static
die "Cannot specify -c or -C when building a dynamic library" if @dylib
die "Cannot specify -c and -C at the same time" if @bundle and @dont_link die "Cannot specify -c and -C at the same time" if @bundle and @dont_link
if @files.size > 1 and @output if @files.size > 1 and @output
die "Cannot specify -o with -c or -C and multiple input files" die "Cannot specify -o with -c or -C and multiple input files"
Expand All @@ -88,18 +88,26 @@ class Compiler
compile_object(file, @output) compile_object(file, @output)
end end
else else
die "Cannot specify --static and --dylib at the same time" if @dylib and @static
objs = @files.map do |file| objs = @files.map do |file|
die "Given input file `#{file} must exist" unless File.exist?(file)
case File.extname(file) case File.extname(file)
when '.rb' when '.rb'
compile_object(file, nil) compile_object(file, nil)
when '.o' when '.o'
die "Given input file `#{file} must exist" unless File.exist?(file) [file, find_init_func(file)]
file when '.dylib'
[file, nil]
else else
die "Given input file `#{file}' must be either a Ruby source file (.rb) or a Mach-O object file (.o)" die "Given input file `#{file}' must be either a Ruby source file (.rb) or a Mach-O object file (.o) or dynamic library (.dylib)"
end end
end end
compile_executable(objs, @output) if @dylib
die "-o must be specified when building a dynamic library" unless @output
compile_dylib(objs, @output)
else
compile_executable(objs, @output)
end
end end
end end


Expand Down Expand Up @@ -149,6 +157,7 @@ class Compiler


output ||= File.join(File.dirname(file), base + '.rbo') output ||= File.join(File.dirname(file), base + '.rbo')


# Generate main file.
main_txt = <<EOS main_txt = <<EOS
extern "C" { extern "C" {
void *#{init_func}(void *, void *); void *#{init_func}(void *, void *);
Expand All @@ -159,18 +168,43 @@ extern "C" {
} }
EOS EOS


# Build.
main = gen_tmpfile('main', 'c')
File.open(main, 'w') { |io| io.write(main_txt) }
linkf = @internal ? "-L. -lmacruby" : "-framework MacRuby"
execute("#{@gcxx} \"#{main}\" -dynamic -bundle -undefined suppress -flat_namespace #{arch_flags} #{linkf} \"#{obj}\" -o \"#{output}\"")
end

def compile_dylib(objs_data, output)
# Generate main file.
main_txt = <<EOS
extern "C" {
void rb_vm_aot_feature_provide(const char *, void *);
EOS
objs_data.each do |obj, init_func|
next if init_func == nil
main_txt << "void *#{init_func}(void *, void *);\n"
end
main_txt << <<EOS
__attribute__((constructor)) static void __init__(void) {
EOS
objs_data.each do |obj, init_func|
main_txt << "rb_vm_aot_feature_provide(\"#{feature_name(obj)}\", (void *)#{init_func});\n"
end
main_txt << "}}"

# Build.
main = gen_tmpfile('main', 'c') main = gen_tmpfile('main', 'c')
File.open(main, 'w') { |io| io.write(main_txt) } File.open(main, 'w') { |io| io.write(main_txt) }
linkf = @internal ? "-L. -lmacruby" : "-framework MacRuby" linkf = @internal ? "-L. -lmacruby" : "-framework MacRuby"
archf = @archs.map { |x| "-arch #{x}" }.join(' ') objs = objs_data.map { |obj, f| "\"#{obj}\"" }.join(' ')
execute("#{@gcxx} \"#{main}\" -dynamic -bundle -undefined suppress -flat_namespace #{archf} #{linkf} \"#{obj}\" -o \"#{output}\"") execute("#{@gcxx} \"#{main}\" -dynamiclib -dynamic -undefined suppress -flat_namespace #{arch_flags} #{linkf} #{objs} -o \"#{output}\"")
end end


def compile_executable(objs_data, output) def compile_executable(objs_data, output)
output ||= 'a.out' output ||= 'a.out'
objs = [] raise if objs_data.empty?
init_funcs = [] die "first object file must be a Ruby source file or object" if objs_data[0][1] == nil
objs_data.each { |obj, init_func| objs << obj; init_funcs << init_func }


# Generate main file. # Generate main file.
main_txt = <<EOS main_txt = <<EOS
Expand All @@ -181,11 +215,15 @@ extern "C" {
void ruby_script(const char *); void ruby_script(const char *);
void ruby_set_argv(int, char **); void ruby_set_argv(int, char **);
void rb_vm_init_compiler(void); void rb_vm_init_compiler(void);
void rb_vm_aot_feature_provide(const char *, void *);
void *rb_vm_top_self(void); void *rb_vm_top_self(void);
void rb_vm_print_current_exception(void); void rb_vm_print_current_exception(void);
void rb_exit(int); void rb_exit(int);
EOS EOS
init_funcs.each { |x| main_txt << "void *#{x}(void *, void *);\n" } objs_data.each do |obj, init_func|
next if init_func == nil
main_txt << "void *#{init_func}(void *, void *);\n"
end
main_txt << <<EOS main_txt << <<EOS
} }
Expand All @@ -203,10 +241,14 @@ int main(int argc, char **argv)
rb_vm_init_compiler(); rb_vm_init_compiler();
ruby_script(progname); ruby_script(progname);
try { try {
void *self = rb_vm_top_self();
EOS EOS
init_funcs.each { |x| main_txt << "#{x}(self, 0);\n" } objs_data[1..-1].each do |obj, init_func|
next if init_func == nil
main_txt << "rb_vm_aot_feature_provide(\"#{feature_name(obj)}\", (void *)#{init_func});\n"
end
main_txt << <<EOS main_txt << <<EOS
void *self = rb_vm_top_self();
#{objs_data[0][1]}(self, 0);
} }
catch (...) { catch (...) {
rb_vm_print_current_exception(); rb_vm_print_current_exception();
Expand All @@ -216,20 +258,22 @@ EOS
} }
EOS EOS


# Prepare objects.
objs = []
objs_data.each { |o, _| objs << o }

# Compile main file. # Compile main file.
main = gen_tmpfile('main', 'mm') main = gen_tmpfile('main', 'mm')
File.open(main, 'w') { |io| io.write(main_txt) } File.open(main, 'w') { |io| io.write(main_txt) }
main_o = gen_tmpfile('main', 'o') main_o = gen_tmpfile('main', 'o')
archf = @archs.map { |x| "-arch #{x}" }.join(' ') execute("#{@gcxx} \"#{main}\" -c #{arch_flags} -o \"#{main_o}\" -fobjc-gc")
execute("#{@gcxx} \"#{main}\" -c #{archf} -o \"#{main_o}\" -fobjc-gc")
objs.unshift(main_o) objs.unshift(main_o)


# Link all objects into executable. # Link all objects into executable.
linkf = @static ? linkf = @static ?
"-L#{RbConfig::CONFIG['libdir']} #{RbConfig::CONFIG['LIBRUBYARG_STATIC_REALLY']}" : "-L#{RbConfig::CONFIG['libdir']} #{RbConfig::CONFIG['LIBRUBYARG_STATIC_REALLY']}" :
"-framework MacRuby -lobjc" "-framework MacRuby -lobjc"
line = "#{@gcxx} -o \"#{output}\" #{archf} #{linkf} " line = "#{@gcxx} -o \"#{output}\" #{arch_flags} #{linkf} "
@frameworks.each { |f| line << "-framework #{f} " }
objs.each { |o| line << " \"#{o}\"" } objs.each { |o| line << " \"#{o}\"" }
execute(line) execute(line)
end end
Expand Down Expand Up @@ -266,6 +310,25 @@ EOS
end end
end end


def arch_flags
@archs.map { |x| "-arch #{x}" }.join(' ')
end

def find_init_func(obj)
output = `#{@nm} -j "#{obj}"`
output.scan(/^_MREP_.*$/).reject { |func|
# Ignore non-main functions.
func.include?('ruby_scope')
}.map { |func|
# Ignore the very first character (_).
func[1..-1]
}[0]
end

def feature_name(obj)
obj.sub(/#{File.extname(obj)}$/, '')
end

def gen_tmpfile(base, ext) def gen_tmpfile(base, ext)
file = File.join(@tmpdir, "#{base}.#{ext}") file = File.join(@tmpdir, "#{base}.#{ext}")
@tmpfiles << file @tmpfiles << file
Expand Down
6 changes: 6 additions & 0 deletions load.c
Expand Up @@ -265,6 +265,12 @@ rb_require_safe(VALUE fname, int safe)
VALUE path; VALUE path;
int type = 0; int type = 0;


// Immediately, check out if we have an AOT feature for this.
if (rb_vm_aot_feature_load(RSTRING_PTR(fname))) {
rb_provide_feature(fname);
return Qtrue;
}

FilePathValue(fname); FilePathValue(fname);
if (search_required(fname, &path, &type)) { if (search_required(fname, &path, &type)) {
if (path == 0) { if (path == 0) {
Expand Down
36 changes: 36 additions & 0 deletions vm.cpp
Expand Up @@ -4807,6 +4807,42 @@ rb_vm_unregister_current_alien_thread(void)
} }
} }


// AOT features. These are registered at runtime once an AOT object file
// is loaded, either directly from an executable's main() function or from
// a gcc constructor (in case of a dylib).
//
// XXX this shared map is not part of RoxorCore because gcc constructors can
// potentially be called *before* RoxorCore has been initialized. This is
// definitely not thread-safe, but it shouldn't be a big deal at this point.
static std::map<std::string, void *> aot_features;

extern "C"
bool
rb_vm_aot_feature_load(const char *name)
{
std::string key(name);
std::map<std::string, void *>::iterator iter = aot_features.find(name);
if (iter == aot_features.end()) {
return false;
}
void *init_func = iter->second;
((void *(*)(void *, void *))init_func)((void *)rb_vm_top_self(), NULL);
aot_features.erase(iter);
return true;
}

extern "C"
void
rb_vm_aot_feature_provide(const char *name, void *init_func)
{
std::string key(name);
std::map<std::string, void *>::iterator iter = aot_features.find(key);
if (iter != aot_features.end()) {
printf("WARNING: AOT feature '%s' already registered, new one will be ignored. This could happen if you link your executable against dylibs that contain the same Ruby file.\n", name);
}
aot_features[key] = init_func;
}

extern "C" extern "C"
void void
Init_PostVM(void) Init_PostVM(void)
Expand Down
9 changes: 7 additions & 2 deletions vm.h
Expand Up @@ -413,6 +413,8 @@ Class rb_vm_get_current_class(void);
bool rb_vm_is_multithreaded(void); bool rb_vm_is_multithreaded(void);
void rb_vm_set_multithreaded(bool flag); void rb_vm_set_multithreaded(bool flag);


bool rb_vm_aot_feature_load(const char *name);

static inline VALUE static inline VALUE
rb_robject_allocate_instance(VALUE klass) rb_robject_allocate_instance(VALUE klass)
{ {
Expand Down Expand Up @@ -584,11 +586,13 @@ class RoxorCore {
// be empty when we exit and we need to call the remaining finalizers. // be empty when we exit and we need to call the remaining finalizers.
std::vector<rb_vm_finalizer_t *> finalizers; std::vector<rb_vm_finalizer_t *> finalizers;


// The global lock.
pthread_mutex_t gl;

// State. // State.
bool running; bool running;
bool multithreaded; bool multithreaded;
bool abort_on_exception; bool abort_on_exception;
pthread_mutex_t gl;
VALUE loaded_features; VALUE loaded_features;
VALUE load_path; VALUE load_path;
VALUE rand_seed; VALUE rand_seed;
Expand Down Expand Up @@ -803,7 +807,8 @@ class RoxorCore {
void invalidate_respond_to_cache(void) { void invalidate_respond_to_cache(void) {
respond_to_cache.clear(); respond_to_cache.clear();
} }
bool respond_to(VALUE obj, VALUE klass, SEL sel, bool priv, bool check_override); bool respond_to(VALUE obj, VALUE klass, SEL sel, bool priv,
bool check_override);


private: private:
bool register_bs_boxed(bs_element_type_t type, void *value); bool register_bs_boxed(bs_element_type_t type, void *value);
Expand Down

0 comments on commit b451db6

Please sign in to comment.