mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-25 14:52:21 +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/Cargo.lock
|
||||||
/examples/rust-simple/build
|
/examples/rust-simple/build
|
||||||
/examples/rust-simple/target
|
/examples/rust-simple/target
|
||||||
|
/examples/rust-string/Cargo.lock
|
||||||
|
/examples/rust-string/build
|
||||||
|
/examples/rust-string/target
|
||||||
|
22
build.gradle
22
build.gradle
@ -60,12 +60,19 @@ project(':examples') {
|
|||||||
compileOnly project(':compiler')
|
compileOnly project(':compiler')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ext.rustBuildRelease = true
|
||||||
|
|
||||||
task rustToWasm(type: Exec) {
|
task rustToWasm(type: Exec) {
|
||||||
|
if (rustBuildRelease) {
|
||||||
commandLine 'cargo', 'build', '--release'
|
commandLine 'cargo', 'build', '--release'
|
||||||
|
} else {
|
||||||
|
commandLine 'cargo', 'build'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.wasmFileName = { ->
|
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())
|
if (wasmFiles.size() != 1) throw new GradleException('Expected single WASM file, got ' + wasmFiles.size())
|
||||||
return wasmFiles.iterator().next()
|
return wasmFiles.iterator().next()
|
||||||
}
|
}
|
||||||
@ -112,3 +119,16 @@ project(':examples:rust-simple') {
|
|||||||
|
|
||||||
mainClassName = 'asmble.examples.rustsimple.Main'
|
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
|
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):
|
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
|
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`
|
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',
|
include 'annotations',
|
||||||
'compiler',
|
'compiler',
|
||||||
'emscripten-runtime',
|
'emscripten-runtime',
|
||||||
'examples:rust-simple'
|
'examples:rust-simple',
|
||||||
|
'examples:rust-string'
|
Loading…
x
Reference in New Issue
Block a user