-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Rust support #37
Add Rust support #37
Conversation
Woaw, that's a nice contribution, thanks! We'll look into it as soon as possible. |
aab2b54
to
4187789
Compare
I've now updated this PR with a new generic struct |
tools/generator/files/rules.mk
Outdated
|
||
ld_flags = $(_LDFLAGS) | ||
cmd_ld_shared = $(CXX) $(filter %.o %.a,$^) $(ld_flags) -shared -o $@ $(_LDLIBS) | ||
ifeq ($(STECHEC_LANG),rust) | ||
cmd_ld_shared = $(CXX) -Wl,--whole-archive $(filter %.o %.a,$^) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't all of those go directly into $ldflags?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is the -Wl,--whole-archive
argument which is required to be before the libraries linked.
I could add a pre-ld-flags
variable to generalize this, but I'm not very proficient in Makefiles writing, and this is a very special case of cmd_ld_shared
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think LDFLAGS is supposed to be before the objects, and what goes after is in LDLIBS. Can you still compile the other champions if you just put
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tested and it works for every language, except php and haskell, because I've not managed to build them.
tools/generator/gen/generator_c.rb
Outdated
def build | ||
@path = Pathname.new($install_path) + "c" | ||
def build(lang = "c") | ||
@path = Pathname.new($install_path) + lang |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that the design here isn't ideal, but I think it would still be a bit more elegant to subclass CCxxFileGenerator and override build() there.
|
||
/// Called 10K times to test if things work well. | ||
#[no_mangle] | ||
pub unsafe extern "C" fn test() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally I'd think it would be great to have a wrapper that does the extern "C" and the demangling, and those wrappers would call the user functions, which I think would be a bit prettier (and less boilerplate for the contestant). If you don't have time to do it, that's fine, but that would be a good improvement.
I added a few comments of things that could be better. Just so you know, we're definitely planning on testing that for next week's finals. |
This reuses part of the C generator, by using a C++ <=> C <=> Rust FFI. The linking is done with whole-archive to properly merge C/C++ and Rust static libraries. Related to prologin/sadm#102.
@seirl I think I've now addressed all the comments made so far. Diff with previous version: diff --git a/tools/generator/files/rules.mk b/tools/generator/files/rules.mk
index 4e77b20..914ce32 100644
--- a/tools/generator/files/rules.mk
+++ b/tools/generator/files/rules.mk
@@ -183,13 +183,7 @@ cmd_ghc = $(GHC) -pgml $(CXX) -O9 -dynamic --make -shared -fPIC \
cmd_rustc = $(RUSTC) $(rustc_flags) $< -o $@
ld_flags = $(_LDFLAGS)
-ifeq ($(STECHEC_LANG),rust)
- cmd_ld_shared = $(CXX) -Wl,--whole-archive $(filter %.o %.a,$^) \
- -Wl,--no-whole-archive -lm -lrt -ldl -lpthread -lstdc++ \
- -shared -fPIC -o $@ $(_LDLIBS)
-else
- cmd_ld_shared = $(CXX) $(filter %.o %.a,$^) $(ld_flags) -shared -o $@ $(_LDLIBS)
-endif
+cmd_ld_shared = $(CXX) $(ld_flags) $(filter %.o %.a,$^) $(_LDLIBS) -shared -o $@
cmd_clean = $(RM) $(_cleanfiles)
cmd_distclean = $(RM) $(_dcleanfiles)
diff --git a/tools/generator/gen/generator_c.rb b/tools/generator/gen/generator_c.rb
index 0a397ce..b7cc5a1 100644
--- a/tools/generator/gen/generator_c.rb
+++ b/tools/generator/gen/generator_c.rb
@@ -232,8 +232,8 @@ EOF
@f.close
end
- def build(lang = "c")
- @path = Pathname.new($install_path) + lang
+ def build
+ @path = Pathname.new($install_path) + "c"
@source_file = 'interface.cc'
@header_file = 'interface.hh'
diff --git a/tools/generator/gen/generator_rust.rb b/tools/generator/gen/generator_rust.rb
index 72c9e16..863cedf 100644
--- a/tools/generator/gen/generator_rust.rb
+++ b/tools/generator/gen/generator_rust.rb
@@ -35,6 +35,17 @@ def rust_proto(fn)
"fn #{fn.name}(#{args})" + ret
end
+class CCxxFileGeneratorForRust < CCxxFileGenerator
+ def build
+ @path = Pathname.new($install_path) + "rust"
+ @source_file = 'interface.cc'
+ @header_file = 'interface.hh'
+
+ generate_source
+ generate_header
+ end
+end
+
class RustFileGenerator < FileGenerator
def print_multiline_comment(str, prestr = '')
str.each_line {|s| @f.print prestr, "// ", s }
@@ -116,18 +127,23 @@ EOF
@f.print "pub ", rust_proto(f), ";\n"
end
@f.puts "}", ""
+ for_each_user_fun do |f|
+ @f.puts"#[no_mangle]"
+ @f.print "pub unsafe extern \"C\" ", rust_proto(f)
+ args = f.args.map { |arg| arg.name }.join(", ")
+ print_body " super::#{f.name}(#{args})"
+ end
@f.close
end
def generate_user
@f = File.open(@path + @user_file, 'w')
@f.puts "#![allow(unused)]"
- @f.puts "mod api;"
+ @f.puts "pub mod api;"
@f.puts "use api::*;"
@f.puts "use std::os::raw::{c_double, c_int, c_void};", ""
for_each_user_fun do |f|
- @f.puts"#[no_mangle]"
- @f.print "pub extern \"C\" ", rust_proto(f)
+ @f.print rust_proto(f)
print_body " // fonction a completer"
end
@f.close
@@ -148,6 +164,9 @@ lib_TARGETS = #{target}
# Evite de toucher a ce qui suit
#{target}-dists += #{$conf['conf']['player_filename']}.h interface.hh #{@api_file}
#{target}-srcs += interface.cc #{@user_file}
+#{target}-ldflags = -fPIC -Wl,--whole-archive
+#{target}-ldlibs = -Wl,--no-whole-archive -lm -lrt -ldl -lpthread -lstdc++
+
STECHEC_LANG=rust
include ../includes/rules.mk
EOF
@@ -156,7 +175,7 @@ include ../includes/rules.mk
def build()
- CCxxFileGenerator.new.build("rust")
+ CCxxFileGeneratorForRust.new.build
@path = Pathname.new($install_path) + "rust"
@user_file = $conf['conf']['player_filename'] + '.rs'
diff --git a/tools/generator/test/champions/prologin.rs b/tools/generator/test/champions/prologin.rs
index 2dee6b5..4440852 100644
--- a/tools/generator/test/champions/prologin.rs
+++ b/tools/generator/test/champions/prologin.rs
@@ -1,12 +1,11 @@
#![allow(bad_style, unused)]
-mod api;
+pub mod api;
use api::*;
use std::os::raw::{c_double, c_int, c_void};
use std::slice::from_raw_parts;
/// Called 10K times to test if things work well.
-#[no_mangle]
-pub unsafe extern "C" fn test()
+unsafe fn test()
{
assert!(CONST_VAL/4 == 10);
assert!(CONST_DOUBLE_2/2. == 668.5); |
Thanks, looking into that. Btw, I'd much rather have multiple commits that we squash when merging the PR than diffs in the comments ;-) |
I think you're missing the makefile_rust.rb? |
@seirl Thank you very much for your review, and I'll try not to squash commits in the future. It seems that |
Yeah, there are two different makefiles that we need to have. The one you're refering to is used for local matches, but the server needs stechec2/tools/generator/generator.rb Lines 119 to 145 in e1c6e61
|
@seirl Thanks for this hint, I've now pushed a new commit adding that file, but I was unable to find a way to test it. |
This looks good, I'll try to test that for you from my local setup, don't worry. |
Merging to ease internal testing, we'll iterate from this if we find any issues. |
@seirl Thanks again for your review! |
@MaloJaffre All API functions are currently unsafe, making them quite painful to use. One solution is to wrap all function calls in an unsafe block, allowing the user to use directly the functions. Does this look good to you? If yes, I'll do it asap. |
@Namarand Good idea! |
@MaloJaffre I really don't like the Array implementation. I think the wrapper function should copy the array in a native rust array, we really don't want a free(). |
@MaloJaffre I think the main problem is that you used the C api, so instead of doing a nice conversion C++ -> Rust it does C++ -> C -> Rust, so you have to go through all the hacks that are made just to have usable arrays in C. I would like it a lot more if you could just populate your Rust arrays from the C++ vectors. Do you think that's doable? |
@seirl I will try to do it by directly allocating a Rust |
It's not a problem to pass through extern "C" functions, but I just don't think you should use the C generator. The C++ generator already contains those unmangled function exports. |
Actually, https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html does quite a good job for converting C array to slice. Maybe we can use that ? We can also convert them to Vec<> thanx to the to_vec() method. |
As discussed in prologin#37. This doesen't yet eliminate the C layer, because it seems very difficult to allocate and construct in C++ a `Vec<T>` (its layout is unspecified so that its fields can be reordered by rustc, and it is incompatible with malloced buffers), but all these horrible `Array<T>` are now hidden in a new `ffi` module. Nevertheless, this greatly simplifies the API usage, as seen in the `prologin.rs` test.
As discussed in #37. This doesen't yet eliminate the C layer, because it seems very difficult to allocate and construct in C++ a `Vec<T>` (its layout is unspecified so that its fields can be reordered by rustc, and it is incompatible with malloced buffers), but all these horrible `Array<T>` are now hidden in a new `ffi` module. Nevertheless, this greatly simplifies the API usage, as seen in the `prologin.rs` test.
This reuses part of the C generator, by using a C++ <=> C <=> Rust FFI.
The linking is done with whole-archive to properly merge C/C++ and Rust static libraries.
testall.sh
succeeds on my machine, and I've manually verified the correctness of the generatedprologin2017
environment.Related to prologin/sadm#102.