Rust string example for issue #9

This commit is contained in:
Chad Retz 2017-11-29 13:03:36 -06:00
parent e51da3116e
commit 8b51e14c33
11 changed files with 221 additions and 4 deletions

3
.gitignore vendored
View File

@ -13,3 +13,6 @@
/examples/rust-simple/Cargo.lock
/examples/rust-simple/build
/examples/rust-simple/target
/examples/rust-string/Cargo.lock
/examples/rust-string/build
/examples/rust-string/target

View File

@ -60,12 +60,19 @@ project(':examples') {
compileOnly project(':compiler')
}
ext.rustBuildRelease = true
task rustToWasm(type: Exec) {
commandLine 'cargo', 'build', '--release'
if (rustBuildRelease) {
commandLine 'cargo', 'build', '--release'
} else {
commandLine 'cargo', 'build'
}
}
ext.wasmFileName = { ->
def wasmFiles = fileTree(dir: 'target/wasm32-unknown-unknown/release', includes: ['*.wasm']).files
def buildType = rustBuildRelease ? 'release' : 'debug'
def wasmFiles = fileTree(dir: "target/wasm32-unknown-unknown/$buildType", includes: ['*.wasm']).files
if (wasmFiles.size() != 1) throw new GradleException('Expected single WASM file, got ' + wasmFiles.size())
return wasmFiles.iterator().next()
}
@ -111,4 +118,17 @@ project(':examples:rust-simple') {
}
mainClassName = 'asmble.examples.rustsimple.Main'
}
project(':examples:rust-string') {
apply plugin: 'application'
ext.wasmCompiledClassName = 'asmble.generated.RustString'
dependencies {
compile files('build/wasm-classes')
}
compileJava {
dependsOn compileWasm
}
mainClassName = 'asmble.examples.ruststring.Main'
}

9
examples/README.md Normal file
View File

@ -0,0 +1,9 @@
## Examples
### Rust
In order of complexity:
* [rust-simple](rust-simple)
* [rust-string](rust-string)
* rust-regex

View File

@ -12,7 +12,7 @@ to `asmble.generated.RustSimple` in `build/wasm-classes`. The class is used by
To run it yourself, run the following from the root `asmble` dir (assuming you have built the Gradle wrapper described
in the root README's "Building and Testing" section):
./gradlew --no-daemon :examples:rust-simple:run
gradlew --no-daemon :examples:rust-simple:run
Yes, this does include Rust's std lib, but it's not that big of a deal (I'm keeping it around because in other examples
as part of [issue #9](https://github.com/cretz/asmble/issues/9) I'll need it). The actual method executed for `add_one`

View File

@ -0,0 +1,2 @@
[build]
target = "wasm32-unknown-unknown"

View File

@ -0,0 +1,6 @@
[package]
name = "rust_string"
version = "0.1.0"
[lib]
crate-type = ["cdylib"]

View File

@ -0,0 +1,20 @@
### Example: Rust String
This shows an example of using Rust strings on the JVM. This builds on [rust-simple](../rust-simple).
In this version, we do allocation and deallocation on the Rust side and we have a Java pointer object that deallocates
on finalization. Inside of Rust, we make sure not to take ownership of any of the data. To demonstrate string use, we
implement two functions on the Rust side: one for string length and another for prepending "From Rust: ". Both don't
take strings directly, but instead pointers and lengths to the byte arrays.
To run it yourself, run the following from the root `asmble` dir:
gradlew --no-daemon :examples:rust-string:run
In release mode, the generated class is 128KB w/ a bit over 200 methods, but it is quite fast. The output:
Char count of 'tester': 6
Char count of Russian hello (Здравствуйте): 12
From Rust: Hello, World!
For me on Windows, the Russian word above just appears as `????????????`, but the rest is right.

View File

@ -0,0 +1,46 @@
#![feature(allocator_api)]
use std::heap::{Alloc, Heap, Layout};
use std::ffi::{CString};
use std::mem;
use std::os::raw::c_char;
use std::str;
#[no_mangle]
pub extern "C" fn string_len(ptr: *mut u8, len: usize) -> usize {
unsafe {
let bytes = Vec::<u8>::from_raw_parts(ptr, len, len);
let len = str::from_utf8(&bytes).unwrap().chars().count();
mem::forget(bytes);
len
}
}
#[no_mangle]
pub extern "C" fn prepend_from_rust(ptr: *mut u8, len: usize) -> *const c_char {
unsafe {
let bytes = Vec::<u8>::from_raw_parts(ptr, len, len);
let s = str::from_utf8(&bytes).unwrap();
mem::forget(s);
let cstr = CString::new(format!("From Rust: {}", s)).unwrap();
let ret = cstr.as_ptr();
mem::forget(cstr);
return ret
}
}
#[no_mangle]
pub extern "C" fn alloc(size: usize) -> *mut u8 {
unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.alloc(layout).unwrap()
}
}
#[no_mangle]
pub extern "C" fn dealloc(ptr: *mut u8, size: usize) {
unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.dealloc(ptr, layout);
}
}

View File

@ -0,0 +1,93 @@
package asmble.examples.ruststring;
import asmble.generated.RustString;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
// Not thread safe!
class Lib {
private final RustString rustString;
Lib(RustString rustString) {
this.rustString = rustString;
}
int stringLength(String str) {
Ptr strPtr = ptrFromString(str);
return rustString.string_len(strPtr.offset, strPtr.size);
}
String prependFromRust(String str) {
Ptr strPtr = ptrFromString(str);
int nullTermOffset = rustString.prepend_from_rust(strPtr.offset, strPtr.size);
return nullTermedStringFromOffset(nullTermOffset);
}
private Ptr ptrFromString(String str) {
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
Ptr ptr = new Ptr(bytes.length);
ptr.put(bytes);
return ptr;
}
private String nullTermedStringFromOffset(int offset) {
ByteBuffer memory = rustString.getMemory();
memory.position(offset);
// We're going to turn the mem into an input stream. This is the
// reasonable way to stream a UTF8 read using standard Java libs
// that I could find.
InputStreamReader r = new InputStreamReader(new InputStream() {
@Override
public int read() throws IOException {
if (!memory.hasRemaining()) {
return -1;
}
return memory.get() & 0xFF;
}
}, StandardCharsets.UTF_8);
StringBuilder builder = new StringBuilder();
try {
while (true) {
int c = r.read();
if (c <= 0) {
break;
}
builder.append((char) c);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
rustString.dealloc(offset, memory.position() - offset);
return builder.toString();
}
class Ptr {
final int offset;
final int size;
Ptr(int offset, int size) {
this.offset = offset;
this.size = size;
}
Ptr(int size) {
this(rustString.alloc(size), size);
}
void put(byte[] bytes) {
// Yeah, yeah, not thread safe
ByteBuffer memory = rustString.getMemory();
memory.position(offset);
memory.put(bytes);
}
@Override
protected void finalize() throws Throwable {
rustString.dealloc(offset, size);
}
}
}

View File

@ -0,0 +1,17 @@
package asmble.examples.ruststring;
import asmble.generated.RustString;
public class Main {
// 20 pages is good for now
private static final int PAGE_SIZE = 65536;
private static final int MAX_MEMORY = 20 * PAGE_SIZE;
public static void main(String[] args) {
Lib lib = new Lib(new RustString(MAX_MEMORY));
System.out.println("Char count of 'tester': " + lib.stringLength("tester"));
String russianHello = "\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439\u0442\u0435";
System.out.println("Char count of Russian hello (" + russianHello + "): " + lib.stringLength(russianHello));
System.out.println(lib.prependFromRust("Hello, World!"));
}
}

View File

@ -2,4 +2,5 @@ rootProject.name = 'asmble'
include 'annotations',
'compiler',
'emscripten-runtime',
'examples:rust-simple'
'examples:rust-simple',
'examples:rust-string'