I couldn't find any good and ergonomic way to generate WASM GC code, and writing it by hand is kinda tedious, so I created a project to help with that: Tarnik. It's an early stage, but I like getting feedback early, so I decided to open source it already. The primary aim is to use it in jawsm and I think I will be able to do it soon. There is an example in the README and a few more in the tests. I plan to add more stuff in the next few weeks as I would really like to start using it in jawsm soon.
Let me know what you think!
I figured out I might use this thread as a way to write about updates to the tool. It's too early to create any official releases I think, but recently I've added a few more things:
!
and &&
+ ||
)So this compiles successfully now:
let module = tarnik::wasm! {
#[export("memory")]
memory!("memory", 1);
type String = [mut i8];
#[export("_start")]
fn run() {
let foo: String = "Hello world";
foo[1] = 'a';
let sum: i32 = 0;
for byte in foo {
sum += byte;
}
}
};
println!("{module}");
Next thing to implement is memory access, which shouldn't be too hard, and then ref.cast
+ ref.test
, which should be enough for me to actually start using it in Jawsm :fingers_crossed:
Another small update. I implemented casting (like: i as i64
), memory store/load support and imports, so now you can write fully functioning WASI program (using p1 for simplicity):
tarnik::wasm! {
#[export("memory")]
memory!("memory", 1);
#[import("wasi_snapshot_preview1", "fd_write")]
fn write(a1: i32, a2: i32, a3: i32, a4: i32) -> i32;
type ImmutableString = [i8];
#[export("_start")]
fn run() {
let str: ImmutableString = "Hello world!";
let i: i32 = 100;
for c in str {
memory[i] = c;
i += 1;
}
// store io vectors
memory[0] = 100;
memory[4] = i;
// `let: foo`` is small hack, if a function returns a value it needs to be somehow consumed
let foo: i32 = write(
1, // stdout
0, // io vectors start
1, // number of io vectors
50, // where to write the result
);
}
};
Just chiming in to say this is pretty awesome, enjoying reading along :)
Thanks @Victor Adossi!
In the recent few days I've added quite a few things:
ref.test
with ref_test!
macro examplelen!()
macro for getting length of an arraydata!()
macro, for example data!["foo"]
will insert a data
entry with the string "foo" and it will return an offset example (I still need to implement getting data entry length)Another update, and this one is exciting (at least for me). Till now I've been working on Tarnik without any real world usage, but the plan was always to use it in my other project: Jawsm compiler. I just finished rewriting about 3k lines of WAT code into about 1.5k of Rust-like code PR. I think this is a good stress test, that also uncovered a few issues, like for example problems when using expressions with casting (like -1 as i32 as i31ref
) and a few other edge cases. I restructured the code to fix some of them, but I also fixed other issues so the code I already had just worked as it should. The list of latest changes:
for
loop)-
and not !
operators for numeric types&&
and ||
for numeric types (it still doesn't work for refs, but I plan to implement it at some point as a ref_test
for null values)(anyobject as Number).value = 1 as f64
. before only a_struct.field = value
was possiblecontinue
and break
(1 + 2) as i64
ref_test!
. It was only capable of checking a path like:ref_test!(number, Number)
, now it will accept any expression, like: ref_test!(numbers[1].value, Number)
memory::<i8>[0]
. The default is to use i32
store and load versionsFor now I will probably slow down development of new features as I will shift my focus a bit towards Jawsm, but I'll try to improve the crate whenever I have time.
Last updated: Dec 23 2024 at 13:07 UTC