mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-24 22:32:19 +00:00
Rust string example for issue #9
This commit is contained in:
parent
e51da3116e
commit
8b51e14c33
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
24
build.gradle
24
build.gradle
@ -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
9
examples/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
## Examples
|
||||
|
||||
### Rust
|
||||
|
||||
In order of complexity:
|
||||
|
||||
* [rust-simple](rust-simple)
|
||||
* [rust-string](rust-string)
|
||||
* rust-regex
|
@ -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`
|
||||
|
2
examples/rust-string/.cargo/config
Normal file
2
examples/rust-string/.cargo/config
Normal file
@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
6
examples/rust-string/Cargo.toml
Normal file
6
examples/rust-string/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "rust_string"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
20
examples/rust-string/README.md
Normal file
20
examples/rust-string/README.md
Normal 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.
|
46
examples/rust-string/src/lib.rs
Normal file
46
examples/rust-string/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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!"));
|
||||
}
|
||||
}
|
@ -2,4 +2,5 @@ rootProject.name = 'asmble'
|
||||
include 'annotations',
|
||||
'compiler',
|
||||
'emscripten-runtime',
|
||||
'examples:rust-simple'
|
||||
'examples:rust-simple',
|
||||
'examples:rust-string'
|
Loading…
x
Reference in New Issue
Block a user