Re-structure and decouple AST from generator

This commit is contained in:
Rerumu 2022-02-08 17:39:14 -05:00
parent 9d2d8aa69b
commit 22ea8910ad
32 changed files with 753 additions and 481 deletions

View File

@ -1,2 +1,8 @@
[workspace] [workspace]
members = ["fuzz", "wasm"] members = [
"codegen-luajit",
"codegen-luau",
"fuzz",
"wasm-ast",
"wasm-synth"
]

13
codegen-luajit/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "codegen-luajit"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies.wasm-ast]
path = "../wasm-ast"
[dependencies.parity-wasm]
git = "https://github.com/paritytech/parity-wasm.git"
features = ["multi_value", "sign_ext"]

View File

@ -1,8 +1,9 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use crate::ast::node::{AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Function}; use wasm_ast::{
node::{AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Function},
use super::visit::{Driver, Visitor}; visit::{Driver, Visitor},
};
struct Visit { struct Visit {
result: BTreeSet<(&'static str, &'static str)>, result: BTreeSet<(&'static str, &'static str)>,

View File

@ -1,8 +1,9 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use crate::ast::node::{AnyLoad, AnyStore, Function, MemoryGrow, MemorySize}; use wasm_ast::{
node::{AnyLoad, AnyStore, Function, MemoryGrow, MemorySize},
use super::visit::{Driver, Visitor}; visit::{Driver, Visitor},
};
struct Visit { struct Visit {
result: BTreeSet<u8>, result: BTreeSet<u8>,

View File

@ -1,3 +1,2 @@
pub mod localize; pub mod localize;
pub mod memory; pub mod memory;
mod visit;

View File

@ -1,27 +1,151 @@
use std::{collections::BTreeSet, io::Result}; use std::{collections::BTreeSet, io::Result, ops::Range};
use parity_wasm::elements::{External, ImportCountType, Instruction, Internal, Module}; use parity_wasm::elements::{
External, ImportCountType, Instruction, Internal, Module, NameSection, ResizableLimits,
use crate::{
analyzer::{localize, memory},
ast::{
builder::{Arities, Builder},
node::{
AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Backward, Br, BrIf, BrTable, Call,
CallIndirect, Else, Expression, Forward, Function, GetGlobal, GetLocal, If, Memorize,
MemoryGrow, MemorySize, Recall, Return, Select, SetGlobal, SetLocal, Statement, Value,
},
},
}; };
use super::{ use wasm_ast::{
base::{Transpiler, Writer}, builder::{Arities, Builder},
shared::{ node::{
aux_internal_index, write_f32, write_f64, write_func_name, write_memory_init, AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Backward, Br, BrIf, BrTable, Call,
write_parameter_list, write_result_list, write_table_init, write_variable_list, CallIndirect, Else, Expression, Forward, Function, GetGlobal, GetLocal, If, Memorize,
MemoryGrow, MemorySize, Recall, Return, Select, SetGlobal, SetLocal, Statement, Value,
}, },
writer::{Transpiler, Writer},
}; };
use crate::analyzer::{localize, memory};
fn aux_internal_index(internal: Internal) -> u32 {
match internal {
Internal::Function(v) | Internal::Table(v) | Internal::Memory(v) | Internal::Global(v) => v,
}
}
fn new_limit_max(limits: &ResizableLimits) -> String {
match limits.maximum() {
Some(v) => v.to_string(),
None => "0xFFFF".to_string(),
}
}
fn write_table_init(limit: &ResizableLimits, w: Writer) -> Result<()> {
let a = limit.initial();
let b = new_limit_max(limit);
write!(w, "{{ min = {}, max = {}, data = {{}} }}", a, b)
}
fn write_memory_init(limit: &ResizableLimits, w: Writer) -> Result<()> {
let a = limit.initial();
let b = new_limit_max(limit);
write!(w, "rt.allocator.new({}, {})", a, b)
}
fn write_func_name(wasm: &Module, index: u32, offset: u32, w: Writer) -> Result<()> {
let opt = wasm
.names_section()
.and_then(NameSection::functions)
.and_then(|v| v.names().get(index));
write!(w, "FUNC_LIST")?;
if let Some(name) = opt {
write!(w, "--[[{}]]", name)?;
}
write!(w, "[{}] =", index + offset)
}
fn write_in_order(prefix: &str, len: u32, w: Writer) -> Result<()> {
if len == 0 {
return Ok(());
}
write!(w, "{}_{}", prefix, 0)?;
(1..len).try_for_each(|i| write!(w, ", {}_{}", prefix, i))
}
fn write_f32(f: f32, w: Writer) -> Result<()> {
let sign = if f.is_sign_negative() { "-" } else { "" };
if f.is_infinite() {
write!(w, "{}math.huge ", sign)
} else if f.is_nan() {
write!(w, "{}0/0 ", sign)
} else {
write!(w, "{:e} ", f)
}
}
fn write_f64(f: f64, w: Writer) -> Result<()> {
let sign = if f.is_sign_negative() { "-" } else { "" };
if f.is_infinite() {
write!(w, "{}math.huge ", sign)
} else if f.is_nan() {
write!(w, "{}0/0 ", sign)
} else {
write!(w, "{:e} ", f)
}
}
fn write_list(name: &str, len: usize, w: Writer) -> Result<()> {
let hash = len.min(1);
let len = len.saturating_sub(1);
write!(w, "local {} = table_new({}, {})", name, len, hash)
}
fn write_parameter_list(func: &Function, w: Writer) -> Result<()> {
write!(w, "function(")?;
write_in_order("param", func.num_param, w)?;
write!(w, ")")
}
fn write_result_list(range: Range<u32>, w: Writer) -> Result<()> {
if range.is_empty() {
return Ok(());
}
range.clone().try_for_each(|i| {
if i != range.start {
write!(w, ", ")?;
}
write!(w, "reg_{}", i)
})?;
write!(w, " = ")
}
fn write_variable_list(func: &Function, w: Writer) -> Result<()> {
if !func.local_list.is_empty() {
let num_local = func.local_list.len().try_into().unwrap();
write!(w, "local ")?;
write_in_order("loc", num_local, w)?;
write!(w, " = ")?;
for (i, t) in func.local_list.iter().enumerate() {
if i != 0 {
write!(w, ", ")?;
}
write!(w, "ZERO_{} ", t)?;
}
}
if func.num_stack != 0 {
write!(w, "local ")?;
write_in_order("reg", func.num_stack, w)?;
write!(w, " ")?;
}
Ok(())
}
fn write_expression(code: &[Instruction], w: Writer) -> Result<()> { fn write_expression(code: &[Instruction], w: Writer) -> Result<()> {
// FIXME: Badly generated WASM will produce the wrong constant. // FIXME: Badly generated WASM will produce the wrong constant.
for inst in code { for inst in code {
@ -479,12 +603,48 @@ impl Driver for Function {
} }
} }
pub struct LuaJIT<'a> { pub struct Generator<'a> {
wasm: &'a Module, wasm: &'a Module,
arity: Arities, arity: Arities,
} }
impl<'a> LuaJIT<'a> { static RUNTIME: &str = include_str!("../runtime/runtime.lua");
impl<'a> Transpiler<'a> for Generator<'a> {
fn new(wasm: &'a Module) -> Self {
let arity = Arities::new(wasm);
Self { wasm, arity }
}
fn runtime(w: Writer) -> Result<()> {
write!(w, "{}", RUNTIME)
}
fn transpile(&self, w: Writer) -> Result<()> {
write!(w, "local rt = require(\"luajit\")")?;
let func_list = self.build_func_list();
Self::gen_localize(&func_list, w)?;
write!(w, "local ZERO_i32 = 0 ")?;
write!(w, "local ZERO_i64 = 0LL ")?;
write!(w, "local ZERO_f32 = 0.0 ")?;
write!(w, "local ZERO_f64 = 0.0 ")?;
write!(w, "local table_new = require(\"table.new\")")?;
write_list("FUNC_LIST", self.wasm.functions_space(), w)?;
write_list("TABLE_LIST", self.wasm.table_space(), w)?;
write_list("MEMORY_LIST", self.wasm.memory_space(), w)?;
write_list("GLOBAL_LIST", self.wasm.globals_space(), w)?;
self.gen_func_list(&func_list, w)?;
self.gen_start_point(w)
}
}
impl<'a> Generator<'a> {
fn gen_import_of<T>(&self, w: Writer, lower: &str, cond: T) -> Result<()> fn gen_import_of<T>(&self, w: Writer, lower: &str, cond: T) -> Result<()>
where where
T: Fn(&External) -> bool, T: Fn(&External) -> bool,
@ -712,46 +872,3 @@ impl<'a> LuaJIT<'a> {
}) })
} }
} }
fn write_list(name: &str, len: usize, w: Writer) -> Result<()> {
let hash = len.min(1);
let len = len.saturating_sub(1);
write!(w, "local {} = table_new({}, {})", name, len, hash)
}
static LUAJIT_RUNTIME: &str = include_str!("../../runtime/luajit.lua");
impl<'a> Transpiler<'a> for LuaJIT<'a> {
fn new(wasm: &'a Module) -> Self {
let arity = Arities::new(wasm);
Self { wasm, arity }
}
fn runtime(writer: Writer) -> Result<()> {
write!(writer, "{}", LUAJIT_RUNTIME)
}
fn transpile(&self, w: Writer) -> Result<()> {
write!(w, "local rt = require(\"luajit\")")?;
let func_list = self.build_func_list();
Self::gen_localize(&func_list, w)?;
write!(w, "local ZERO_i32 = 0 ")?;
write!(w, "local ZERO_i64 = 0LL ")?;
write!(w, "local ZERO_f32 = 0.0 ")?;
write!(w, "local ZERO_f64 = 0.0 ")?;
write!(w, "local table_new = require(\"table.new\")")?;
write_list("FUNC_LIST", self.wasm.functions_space(), w)?;
write_list("TABLE_LIST", self.wasm.table_space(), w)?;
write_list("MEMORY_LIST", self.wasm.memory_space(), w)?;
write_list("GLOBAL_LIST", self.wasm.globals_space(), w)?;
self.gen_func_list(&func_list, w)?;
self.gen_start_point(w)
}
}

View File

@ -0,0 +1,4 @@
pub static RUNTIME: &str = include_str!("../runtime/runtime.lua");
mod analyzer;
pub mod gen;

13
codegen-luau/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "codegen-luau"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies.wasm-ast]
path = "../wasm-ast"
[dependencies.parity-wasm]
git = "https://github.com/paritytech/parity-wasm.git"
features = ["multi_value", "sign_ext"]

View File

@ -0,0 +1,60 @@
use std::collections::BTreeSet;
use wasm_ast::{
node::{AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Function},
visit::{Driver, Visitor},
};
struct Visit {
result: BTreeSet<(&'static str, &'static str)>,
}
impl Visitor for Visit {
fn visit_any_load(&mut self, v: &AnyLoad) {
let name = v.op.as_name();
self.result.insert(("load", name));
}
fn visit_any_store(&mut self, v: &AnyStore) {
let name = v.op.as_name();
self.result.insert(("store", name));
}
fn visit_any_unop(&mut self, v: &AnyUnOp) {
let name = v.op.as_name();
self.result.insert(name);
}
fn visit_any_binop(&mut self, v: &AnyBinOp) {
if v.op.as_operator().is_some() {
return;
}
let name = v.op.as_name();
self.result.insert(name);
}
fn visit_any_cmpop(&mut self, v: &AnyCmpOp) {
if v.op.as_operator().is_some() {
return;
}
let name = v.op.as_name();
self.result.insert(name);
}
}
pub fn visit(func: &Function) -> BTreeSet<(&'static str, &'static str)> {
let mut visit = Visit {
result: BTreeSet::new(),
};
func.accept(&mut visit);
visit.result
}

View File

@ -0,0 +1,38 @@
use std::collections::BTreeSet;
use wasm_ast::{
node::{AnyLoad, AnyStore, Function, MemoryGrow, MemorySize},
visit::{Driver, Visitor},
};
struct Visit {
result: BTreeSet<u8>,
}
impl Visitor for Visit {
fn visit_any_store(&mut self, _: &AnyStore) {
self.result.insert(0);
}
fn visit_any_load(&mut self, _: &AnyLoad) {
self.result.insert(0);
}
fn visit_memory_size(&mut self, m: &MemorySize) {
self.result.insert(m.memory);
}
fn visit_memory_grow(&mut self, m: &MemoryGrow) {
self.result.insert(m.memory);
}
}
pub fn visit(func: &Function) -> BTreeSet<u8> {
let mut visit = Visit {
result: BTreeSet::new(),
};
func.accept(&mut visit);
visit.result
}

View File

@ -0,0 +1,2 @@
pub mod localize;
pub mod memory;

View File

@ -1,27 +1,150 @@
use std::{collections::BTreeSet, io::Result}; use std::{collections::BTreeSet, io::Result, ops::Range};
use parity_wasm::elements::{External, ImportCountType, Instruction, Internal, Module}; use parity_wasm::elements::{
External, ImportCountType, Instruction, Internal, Module, NameSection, ResizableLimits,
use crate::{
analyzer::{localize, memory},
ast::{
builder::{Arities, Builder},
node::{
AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Backward, Br, BrIf, BrTable, Call,
CallIndirect, Else, Expression, Forward, Function, GetGlobal, GetLocal, If, Memorize,
MemoryGrow, MemorySize, Recall, Return, Select, SetGlobal, SetLocal, Statement, Value,
},
},
}; };
use super::{ use wasm_ast::{
base::{Transpiler, Writer}, builder::{Arities, Builder},
shared::{ node::{
aux_internal_index, write_f32, write_f64, write_func_name, write_memory_init, AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Backward, Br, BrIf, BrTable, Call,
write_parameter_list, write_result_list, write_table_init, write_variable_list, CallIndirect, Else, Expression, Forward, Function, GetGlobal, GetLocal, If, Memorize,
MemoryGrow, MemorySize, Recall, Return, Select, SetGlobal, SetLocal, Statement, Value,
}, },
writer::{Transpiler, Writer},
}; };
use super::analyzer::{localize, memory};
fn aux_internal_index(internal: Internal) -> u32 {
match internal {
Internal::Function(v) | Internal::Table(v) | Internal::Memory(v) | Internal::Global(v) => v,
}
}
fn new_limit_max(limits: &ResizableLimits) -> String {
match limits.maximum() {
Some(v) => v.to_string(),
None => "0xFFFF".to_string(),
}
}
fn write_table_init(limit: &ResizableLimits, w: Writer) -> Result<()> {
let a = limit.initial();
let b = new_limit_max(limit);
write!(w, "{{ min = {}, max = {}, data = {{}} }}", a, b)
}
fn write_memory_init(limit: &ResizableLimits, w: Writer) -> Result<()> {
let a = limit.initial();
let b = new_limit_max(limit);
write!(w, "rt.allocator.new({}, {})", a, b)
}
fn write_func_name(wasm: &Module, index: u32, offset: u32, w: Writer) -> Result<()> {
let opt = wasm
.names_section()
.and_then(NameSection::functions)
.and_then(|v| v.names().get(index));
write!(w, "FUNC_LIST")?;
if let Some(name) = opt {
write!(w, "--[[{}]]", name)?;
}
write!(w, "[{}] =", index + offset)
}
fn write_in_order(prefix: &str, len: u32, w: Writer) -> Result<()> {
if len == 0 {
return Ok(());
}
write!(w, "{}_{}", prefix, 0)?;
(1..len).try_for_each(|i| write!(w, ", {}_{}", prefix, i))
}
fn write_f32(f: f32, w: Writer) -> Result<()> {
let sign = if f.is_sign_negative() { "-" } else { "" };
if f.is_infinite() {
write!(w, "{}math.huge ", sign)
} else if f.is_nan() {
write!(w, "{}0/0 ", sign)
} else {
write!(w, "{:e} ", f)
}
}
fn write_f64(f: f64, w: Writer) -> Result<()> {
let sign = if f.is_sign_negative() { "-" } else { "" };
if f.is_infinite() {
write!(w, "{}math.huge ", sign)
} else if f.is_nan() {
write!(w, "{}0/0 ", sign)
} else {
write!(w, "{:e} ", f)
}
}
fn write_list(name: &str, len: usize, w: Writer) -> Result<()> {
let len = len.saturating_sub(1);
write!(w, "local {} = table.create({})", name, len)
}
fn write_parameter_list(func: &Function, w: Writer) -> Result<()> {
write!(w, "function(")?;
write_in_order("param", func.num_param, w)?;
write!(w, ")")
}
fn write_result_list(range: Range<u32>, w: Writer) -> Result<()> {
if range.is_empty() {
return Ok(());
}
range.clone().try_for_each(|i| {
if i != range.start {
write!(w, ", ")?;
}
write!(w, "reg_{}", i)
})?;
write!(w, " = ")
}
fn write_variable_list(func: &Function, w: Writer) -> Result<()> {
if !func.local_list.is_empty() {
let num_local = func.local_list.len().try_into().unwrap();
write!(w, "local ")?;
write_in_order("loc", num_local, w)?;
write!(w, " = ")?;
for (i, t) in func.local_list.iter().enumerate() {
if i != 0 {
write!(w, ", ")?;
}
write!(w, "ZERO_{} ", t)?;
}
}
if func.num_stack != 0 {
write!(w, "local ")?;
write_in_order("reg", func.num_stack, w)?;
write!(w, " ")?;
}
Ok(())
}
fn write_expression(code: &[Instruction], w: Writer) -> Result<()> { fn write_expression(code: &[Instruction], w: Writer) -> Result<()> {
// FIXME: Badly generated WASM will produce the wrong constant. // FIXME: Badly generated WASM will produce the wrong constant.
for inst in code { for inst in code {
@ -476,12 +599,47 @@ impl Driver for Function {
} }
} }
pub struct Luau<'a> { pub struct Generator<'a> {
wasm: &'a Module, wasm: &'a Module,
arity: Arities, arity: Arities,
} }
impl<'a> Luau<'a> { static RUNTIME: &str = include_str!("../runtime/runtime.lua");
impl<'a> Transpiler<'a> for Generator<'a> {
fn new(wasm: &'a Module) -> Self {
let arity = Arities::new(wasm);
Self { wasm, arity }
}
fn runtime(w: Writer) -> Result<()> {
write!(w, "{}", RUNTIME)
}
fn transpile(&self, w: Writer) -> Result<()> {
write!(w, "local rt = require(script.Runtime)")?;
let func_list = self.build_func_list();
Self::gen_localize(&func_list, w)?;
write!(w, "local ZERO_i32 = 0 ")?;
write!(w, "local ZERO_i64 = 0 ")?;
write!(w, "local ZERO_f32 = 0.0 ")?;
write!(w, "local ZERO_f64 = 0.0 ")?;
write_list("FUNC_LIST", self.wasm.functions_space(), w)?;
write_list("TABLE_LIST", self.wasm.table_space(), w)?;
write_list("MEMORY_LIST", self.wasm.memory_space(), w)?;
write_list("GLOBAL_LIST", self.wasm.globals_space(), w)?;
self.gen_func_list(&func_list, w)?;
self.gen_start_point(w)
}
}
impl<'a> Generator<'a> {
fn gen_import_of<T>(&self, w: Writer, lower: &str, cond: T) -> Result<()> fn gen_import_of<T>(&self, w: Writer, lower: &str, cond: T) -> Result<()>
where where
T: Fn(&External) -> bool, T: Fn(&External) -> bool,
@ -702,44 +860,3 @@ impl<'a> Luau<'a> {
}) })
} }
} }
fn write_list(name: &str, len: usize, w: Writer) -> Result<()> {
let len = len.saturating_sub(1);
write!(w, "local {} = table.create({})", name, len)
}
static LUAU_RUNTIME: &str = include_str!("../../runtime/luau.lua");
impl<'a> Transpiler<'a> for Luau<'a> {
fn new(wasm: &'a Module) -> Self {
let arity = Arities::new(wasm);
Self { wasm, arity }
}
fn runtime(writer: Writer) -> Result<()> {
write!(writer, "{}", LUAU_RUNTIME)
}
fn transpile(&self, w: Writer) -> Result<()> {
write!(w, "local rt = require(script.Runtime)")?;
let func_list = self.build_func_list();
Self::gen_localize(&func_list, w)?;
write!(w, "local ZERO_i32 = 0 ")?;
write!(w, "local ZERO_i64 = 0 ")?;
write!(w, "local ZERO_f32 = 0.0 ")?;
write!(w, "local ZERO_f64 = 0.0 ")?;
write_list("FUNC_LIST", self.wasm.functions_space(), w)?;
write_list("TABLE_LIST", self.wasm.table_space(), w)?;
write_list("MEMORY_LIST", self.wasm.memory_space(), w)?;
write_list("GLOBAL_LIST", self.wasm.globals_space(), w)?;
self.gen_func_list(&func_list, w)?;
self.gen_start_point(w)
}
}

2
codegen-luau/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
mod analyzer;
pub mod gen;

View File

@ -15,8 +15,11 @@ wasm-smith = "0.8.0"
git = "https://github.com/paritytech/parity-wasm.git" git = "https://github.com/paritytech/parity-wasm.git"
features = ["multi_value", "sign_ext"] features = ["multi_value", "sign_ext"]
[dependencies.wasm] [dependencies.wasm-ast]
path = "../wasm" path = "../wasm-ast"
[dependencies.codegen-luajit]
path = "../codegen-luajit"
[[bin]] [[bin]]
name = "full_transpile" name = "full_transpile"

View File

@ -2,7 +2,8 @@
use wasm_smith::Module; use wasm_smith::Module;
use wasm::writer::{base::Transpiler, luajit::LuaJIT}; use codegen_luajit::gen::Generator;
use wasm_ast::writer::Transpiler;
// We are not interested in parity_wasm errors. // We are not interested in parity_wasm errors.
// Only 1 edition should need to be tested too. // Only 1 edition should need to be tested too.
@ -13,7 +14,7 @@ libfuzzer_sys::fuzz_target!(|module: Module| {
Err(_) => return, Err(_) => return,
}; };
LuaJIT::new(&wasm) Generator::new(&wasm)
.transpile(&mut std::io::sink()) .transpile(&mut std::io::sink())
.expect("LuaJIT should succeed"); .expect("LuaJIT should succeed");
}); });

View File

@ -3,10 +3,11 @@
use parity_wasm::elements::Module as WasmModule; use parity_wasm::elements::Module as WasmModule;
use wasm_smith::Module as SmModule; use wasm_smith::Module as SmModule;
use wasm::writer::{base::Transpiler, luajit::LuaJIT}; use codegen_luajit::gen::Generator;
use wasm_ast::writer::Transpiler;
fn fuzz_transformer(wasm: &WasmModule) { fn fuzz_transformer(wasm: &WasmModule) {
let trans = LuaJIT::new(wasm); let trans = Generator::new(wasm);
let _func = trans.build_func_list(); let _func = trans.build_func_list();
} }

View File

@ -5,10 +5,11 @@ use std::io::Result;
use parity_wasm::elements::Module as WasmModule; use parity_wasm::elements::Module as WasmModule;
use wasm_smith::Module as SmModule; use wasm_smith::Module as SmModule;
use wasm::writer::{base::Transpiler, luajit::LuaJIT}; use codegen_luajit::gen::Generator;
use wasm_ast::writer::Transpiler;
fn fuzz_writer(wasm: &WasmModule) -> Result<()> { fn fuzz_writer(wasm: &WasmModule) -> Result<()> {
let trans = LuaJIT::new(wasm); let trans = Generator::new(wasm);
let list = trans.build_func_list(); let list = trans.build_func_list();
trans.gen_func_list(&list, &mut std::io::sink()) trans.gen_func_list(&list, &mut std::io::sink())

View File

@ -1,5 +1,5 @@
[package] [package]
name = "wasm" name = "wasm-ast"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"

View File

@ -3,13 +3,11 @@ use parity_wasm::elements::{
ValueType, ValueType,
}; };
use super::{ use crate::node::{
node::{ AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Backward, BinOp, Br, BrIf, BrTable, Call,
AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Backward, Br, BrIf, BrTable, Call, CallIndirect, CmpOp, Else, Expression, Forward, Function, GetGlobal, GetLocal, If, Load,
CallIndirect, Else, Expression, Forward, Function, GetGlobal, GetLocal, If, Memorize, Memorize, MemoryGrow, MemorySize, Recall, Return, Select, SetGlobal, SetLocal, Statement,
MemoryGrow, MemorySize, Recall, Return, Select, SetGlobal, SetLocal, Statement, Value, Store, UnOp, Value,
},
tag::{BinOp, CmpOp, Load, Store, UnOp},
}; };
struct Arity { struct Arity {
@ -73,6 +71,7 @@ pub struct Arities {
} }
impl Arities { impl Arities {
#[must_use]
pub fn new(parent: &Module) -> Self { pub fn new(parent: &Module) -> Self {
Self { Self {
ex_arity: Arity::new_ex_list(parent), ex_arity: Arity::new_ex_list(parent),
@ -80,10 +79,12 @@ impl Arities {
} }
} }
#[must_use]
pub fn len_in(&self) -> usize { pub fn len_in(&self) -> usize {
self.in_arity.len() self.in_arity.len()
} }
#[must_use]
pub fn len_ex(&self) -> usize { pub fn len_ex(&self) -> usize {
self.ex_arity.len() self.ex_arity.len()
} }
@ -138,6 +139,7 @@ fn load_func_at(wasm: &Module, index: usize) -> &FuncBody {
} }
impl<'a> Builder<'a> { impl<'a> Builder<'a> {
#[must_use]
pub fn new(wasm: &'a Module, other: &'a Arities) -> Builder<'a> { pub fn new(wasm: &'a Module, other: &'a Arities) -> Builder<'a> {
Builder { Builder {
wasm, wasm,
@ -149,6 +151,7 @@ impl<'a> Builder<'a> {
} }
} }
#[must_use]
pub fn consume(mut self, index: usize) -> Function { pub fn consume(mut self, index: usize) -> Function {
let func = load_func_at(self.wasm, index); let func = load_func_at(self.wasm, index);
let arity = &self.other.in_arity[index]; let arity = &self.other.in_arity[index];

View File

@ -1,3 +1,4 @@
pub mod builder; pub mod builder;
pub mod node; pub mod node;
mod tag; pub mod visit;
pub mod writer;

View File

@ -1,3 +1,7 @@
use std::ops::Range;
use parity_wasm::elements::{BrTableData, ValueType};
use std::convert::TryFrom; use std::convert::TryFrom;
use parity_wasm::elements::{Instruction, SignExtInstruction}; use parity_wasm::elements::{Instruction, SignExtInstruction};
@ -22,6 +26,7 @@ pub enum Load {
} }
impl Load { impl Load {
#[must_use]
pub fn as_name(self) -> &'static str { pub fn as_name(self) -> &'static str {
match self { match self {
Self::I32 => "i32", Self::I32 => "i32",
@ -85,6 +90,7 @@ pub enum Store {
} }
impl Store { impl Store {
#[must_use]
pub fn as_name(self) -> &'static str { pub fn as_name(self) -> &'static str {
match self { match self {
Self::I32 => "i32", Self::I32 => "i32",
@ -173,6 +179,7 @@ pub enum UnOp {
} }
impl UnOp { impl UnOp {
#[must_use]
pub fn as_name(self) -> (&'static str, &'static str) { pub fn as_name(self) -> (&'static str, &'static str) {
match self { match self {
Self::Clz_I32 => ("clz", "i32"), Self::Clz_I32 => ("clz", "i32"),
@ -323,6 +330,7 @@ pub enum BinOp {
} }
impl BinOp { impl BinOp {
#[must_use]
pub fn as_operator(self) -> Option<&'static str> { pub fn as_operator(self) -> Option<&'static str> {
let op = match self { let op = match self {
Self::Add_FN => "+", Self::Add_FN => "+",
@ -336,6 +344,7 @@ impl BinOp {
Some(op) Some(op)
} }
#[must_use]
pub fn as_name(self) -> (&'static str, &'static str) { pub fn as_name(self) -> (&'static str, &'static str) {
match self { match self {
Self::Add_I32 => ("add", "i32"), Self::Add_I32 => ("add", "i32"),
@ -464,6 +473,7 @@ pub enum CmpOp {
} }
impl CmpOp { impl CmpOp {
#[must_use]
pub fn as_operator(self) -> Option<&'static str> { pub fn as_operator(self) -> Option<&'static str> {
let op = match self { let op = match self {
Self::Eq_I32 | Self::Eq_I64 | Self::Eq_FN => "==", Self::Eq_I32 | Self::Eq_I64 | Self::Eq_FN => "==",
@ -478,6 +488,7 @@ impl CmpOp {
Some(op) Some(op)
} }
#[must_use]
pub fn as_name(self) -> (&'static str, &'static str) { pub fn as_name(self) -> (&'static str, &'static str) {
match self { match self {
Self::Eq_I32 => ("eq", "i32"), Self::Eq_I32 => ("eq", "i32"),
@ -551,3 +562,189 @@ impl TryFrom<&Instruction> for CmpOp {
Ok(result) Ok(result)
} }
} }
#[derive(Clone)]
pub struct Recall {
pub var: usize,
}
pub struct Select {
pub cond: Box<Expression>,
pub a: Box<Expression>,
pub b: Box<Expression>,
}
pub struct GetLocal {
pub var: u32,
}
pub struct GetGlobal {
pub var: u32,
}
pub struct AnyLoad {
pub op: Load,
pub offset: u32,
pub pointer: Box<Expression>,
}
pub struct MemorySize {
pub memory: u8,
}
pub struct MemoryGrow {
pub memory: u8,
pub value: Box<Expression>,
}
#[derive(Clone, Copy)]
pub enum Value {
I32(i32),
I64(i64),
F32(f32),
F64(f64),
}
pub struct AnyUnOp {
pub op: UnOp,
pub rhs: Box<Expression>,
}
pub struct AnyBinOp {
pub op: BinOp,
pub lhs: Box<Expression>,
pub rhs: Box<Expression>,
}
pub struct AnyCmpOp {
pub op: CmpOp,
pub lhs: Box<Expression>,
pub rhs: Box<Expression>,
}
pub enum Expression {
Recall(Recall),
Select(Select),
GetLocal(GetLocal),
GetGlobal(GetGlobal),
AnyLoad(AnyLoad),
MemorySize(MemorySize),
MemoryGrow(MemoryGrow),
Value(Value),
AnyUnOp(AnyUnOp),
AnyBinOp(AnyBinOp),
AnyCmpOp(AnyCmpOp),
}
impl Expression {
#[must_use]
pub fn is_recalling(&self, wanted: usize) -> bool {
match self {
Expression::Recall(v) => v.var == wanted,
_ => false,
}
}
#[must_use]
pub fn clone_recall(&self) -> Self {
match self {
Expression::Recall(v) => Expression::Recall(v.clone()),
_ => unreachable!("clone_recall called on non-recall"),
}
}
}
pub struct Memorize {
pub var: usize,
pub value: Expression,
}
pub struct Forward {
pub body: Vec<Statement>,
}
pub struct Backward {
pub body: Vec<Statement>,
}
pub struct Else {
pub body: Vec<Statement>,
}
pub struct If {
pub cond: Expression,
pub truthy: Vec<Statement>,
pub falsey: Option<Else>,
}
pub struct Br {
pub target: u32,
}
pub struct BrIf {
pub cond: Expression,
pub target: u32,
}
pub struct BrTable {
pub cond: Expression,
pub data: BrTableData,
}
pub struct Return {
pub list: Vec<Expression>,
}
pub struct Call {
pub func: u32,
pub result: Range<u32>,
pub param_list: Vec<Expression>,
}
pub struct CallIndirect {
pub table: u8,
pub index: Expression,
pub result: Range<u32>,
pub param_list: Vec<Expression>,
}
pub struct SetLocal {
pub var: u32,
pub value: Expression,
}
pub struct SetGlobal {
pub var: u32,
pub value: Expression,
}
pub struct AnyStore {
pub op: Store,
pub offset: u32,
pub pointer: Expression,
pub value: Expression,
}
pub enum Statement {
Unreachable,
Memorize(Memorize),
Forward(Forward),
Backward(Backward),
If(If),
Br(Br),
BrIf(BrIf),
BrTable(BrTable),
Return(Return),
Call(Call),
CallIndirect(CallIndirect),
SetLocal(SetLocal),
SetGlobal(SetGlobal),
AnyStore(AnyStore),
}
pub struct Function {
pub local_list: Vec<ValueType>,
pub num_param: u32,
pub num_stack: u32,
pub body: Forward,
}

View File

@ -1,4 +1,4 @@
use crate::ast::node::{ use crate::node::{
AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Backward, Br, BrIf, BrTable, Call, AnyBinOp, AnyCmpOp, AnyLoad, AnyStore, AnyUnOp, Backward, Br, BrIf, BrTable, Call,
CallIndirect, Else, Expression, Forward, Function, GetGlobal, GetLocal, If, Memorize, CallIndirect, Else, Expression, Forward, Function, GetGlobal, GetLocal, If, Memorize,
MemoryGrow, MemorySize, Recall, Return, Select, SetGlobal, SetLocal, Statement, Value, MemoryGrow, MemorySize, Recall, Return, Select, SetGlobal, SetLocal, Statement, Value,

19
wasm-synth/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "wasm-synth"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies.parity-wasm]
git = "https://github.com/paritytech/parity-wasm.git"
features = ["multi_value", "sign_ext"]
[dependencies.wasm-ast]
path = "../wasm-ast"
[dependencies.codegen-luajit]
path = "../codegen-luajit"
[dependencies.codegen-luau]
path = "../codegen-luau"

9
wasm/src/main.rs → wasm-synth/src/main.rs Executable file → Normal file
View File

@ -1,9 +1,8 @@
use parity_wasm::{deserialize_file, elements::Module}; use parity_wasm::{deserialize_file, elements::Module};
use writer::{base::Transpiler, luajit::LuaJIT, luau::Luau};
mod analyzer; use codegen_luajit::gen::Generator as LuaJIT;
mod ast; use codegen_luau::gen::Generator as Luau;
mod writer; use wasm_ast::writer::Transpiler;
fn parse_module(name: &str) -> Module { fn parse_module(name: &str) -> Module {
let wasm = deserialize_file(name).expect("Failed to parse Wasm file"); let wasm = deserialize_file(name).expect("Failed to parse Wasm file");
@ -33,8 +32,8 @@ fn do_translate(name: &str, file: &str) {
let wasm = &parse_module(file); let wasm = &parse_module(file);
match name.to_lowercase().as_str() { match name.to_lowercase().as_str() {
"luau" => run_translator::<Luau>(wasm),
"luajit" => run_translator::<LuaJIT>(wasm), "luajit" => run_translator::<LuaJIT>(wasm),
"luau" => run_translator::<Luau>(wasm),
_ => panic!("Bad language: {}", name), _ => panic!("Bad language: {}", name),
} }
} }

View File

@ -1,189 +0,0 @@
use std::ops::Range;
use parity_wasm::elements::{BrTableData, ValueType};
use super::tag::{BinOp, CmpOp, Load, Store, UnOp};
#[derive(Clone)]
pub struct Recall {
pub var: usize,
}
pub struct Select {
pub cond: Box<Expression>,
pub a: Box<Expression>,
pub b: Box<Expression>,
}
pub struct GetLocal {
pub var: u32,
}
pub struct GetGlobal {
pub var: u32,
}
pub struct AnyLoad {
pub op: Load,
pub offset: u32,
pub pointer: Box<Expression>,
}
pub struct MemorySize {
pub memory: u8,
}
pub struct MemoryGrow {
pub memory: u8,
pub value: Box<Expression>,
}
#[derive(Clone, Copy)]
pub enum Value {
I32(i32),
I64(i64),
F32(f32),
F64(f64),
}
pub struct AnyUnOp {
pub op: UnOp,
pub rhs: Box<Expression>,
}
pub struct AnyBinOp {
pub op: BinOp,
pub lhs: Box<Expression>,
pub rhs: Box<Expression>,
}
pub struct AnyCmpOp {
pub op: CmpOp,
pub lhs: Box<Expression>,
pub rhs: Box<Expression>,
}
pub enum Expression {
Recall(Recall),
Select(Select),
GetLocal(GetLocal),
GetGlobal(GetGlobal),
AnyLoad(AnyLoad),
MemorySize(MemorySize),
MemoryGrow(MemoryGrow),
Value(Value),
AnyUnOp(AnyUnOp),
AnyBinOp(AnyBinOp),
AnyCmpOp(AnyCmpOp),
}
impl Expression {
pub fn is_recalling(&self, wanted: usize) -> bool {
match self {
Expression::Recall(v) => v.var == wanted,
_ => false,
}
}
pub fn clone_recall(&self) -> Self {
match self {
Expression::Recall(v) => Expression::Recall(v.clone()),
_ => unreachable!("clone_recall called on non-recall"),
}
}
}
pub struct Memorize {
pub var: usize,
pub value: Expression,
}
pub struct Forward {
pub body: Vec<Statement>,
}
pub struct Backward {
pub body: Vec<Statement>,
}
pub struct Else {
pub body: Vec<Statement>,
}
pub struct If {
pub cond: Expression,
pub truthy: Vec<Statement>,
pub falsey: Option<Else>,
}
pub struct Br {
pub target: u32,
}
pub struct BrIf {
pub cond: Expression,
pub target: u32,
}
pub struct BrTable {
pub cond: Expression,
pub data: BrTableData,
}
pub struct Return {
pub list: Vec<Expression>,
}
pub struct Call {
pub func: u32,
pub result: Range<u32>,
pub param_list: Vec<Expression>,
}
pub struct CallIndirect {
pub table: u8,
pub index: Expression,
pub result: Range<u32>,
pub param_list: Vec<Expression>,
}
pub struct SetLocal {
pub var: u32,
pub value: Expression,
}
pub struct SetGlobal {
pub var: u32,
pub value: Expression,
}
pub struct AnyStore {
pub op: Store,
pub offset: u32,
pub pointer: Expression,
pub value: Expression,
}
pub enum Statement {
Unreachable,
Memorize(Memorize),
Forward(Forward),
Backward(Backward),
If(If),
Br(Br),
BrIf(BrIf),
BrTable(BrTable),
Return(Return),
Call(Call),
CallIndirect(CallIndirect),
SetLocal(SetLocal),
SetGlobal(SetGlobal),
AnyStore(AnyStore),
}
pub struct Function {
pub local_list: Vec<ValueType>,
pub num_param: u32,
pub num_stack: u32,
pub body: Forward,
}

View File

@ -1,3 +0,0 @@
mod analyzer;
mod ast;
pub mod writer;

View File

@ -1,4 +0,0 @@
pub mod base;
pub mod luajit;
pub mod luau;
mod shared;

View File

@ -1,130 +0,0 @@
use std::{io::Result, ops::Range};
use parity_wasm::elements::{Internal, Module, NameSection, ResizableLimits};
use crate::ast::node::Function;
use super::base::Writer;
pub fn aux_internal_index(internal: Internal) -> u32 {
match internal {
Internal::Function(v) | Internal::Table(v) | Internal::Memory(v) | Internal::Global(v) => v,
}
}
fn new_limit_max(limits: &ResizableLimits) -> String {
match limits.maximum() {
Some(v) => v.to_string(),
None => "0xFFFF".to_string(),
}
}
pub fn write_table_init(limit: &ResizableLimits, w: Writer) -> Result<()> {
let a = limit.initial();
let b = new_limit_max(limit);
write!(w, "{{ min = {}, max = {}, data = {{}} }}", a, b)
}
pub fn write_memory_init(limit: &ResizableLimits, w: Writer) -> Result<()> {
let a = limit.initial();
let b = new_limit_max(limit);
write!(w, "rt.allocator.new({}, {})", a, b)
}
pub fn write_func_name(wasm: &Module, index: u32, offset: u32, w: Writer) -> Result<()> {
let opt = wasm
.names_section()
.and_then(NameSection::functions)
.and_then(|v| v.names().get(index));
write!(w, "FUNC_LIST")?;
if let Some(name) = opt {
write!(w, "--[[{}]]", name)?;
}
write!(w, "[{}] =", index + offset)
}
pub fn write_in_order(prefix: &str, len: u32, w: Writer) -> Result<()> {
if len == 0 {
return Ok(());
}
write!(w, "{}_{}", prefix, 0)?;
(1..len).try_for_each(|i| write!(w, ", {}_{}", prefix, i))
}
pub fn write_f32(f: f32, w: Writer) -> Result<()> {
let sign = if f.is_sign_negative() { "-" } else { "" };
if f.is_infinite() {
write!(w, "{}math.huge ", sign)
} else if f.is_nan() {
write!(w, "{}0/0 ", sign)
} else {
write!(w, "{:e} ", f)
}
}
pub fn write_f64(f: f64, w: Writer) -> Result<()> {
let sign = if f.is_sign_negative() { "-" } else { "" };
if f.is_infinite() {
write!(w, "{}math.huge ", sign)
} else if f.is_nan() {
write!(w, "{}0/0 ", sign)
} else {
write!(w, "{:e} ", f)
}
}
pub fn write_parameter_list(func: &Function, w: Writer) -> Result<()> {
write!(w, "function(")?;
write_in_order("param", func.num_param, w)?;
write!(w, ")")
}
pub fn write_result_list(range: Range<u32>, w: Writer) -> Result<()> {
if range.is_empty() {
return Ok(());
}
range.clone().try_for_each(|i| {
if i != range.start {
write!(w, ", ")?;
}
write!(w, "reg_{}", i)
})?;
write!(w, " = ")
}
pub fn write_variable_list(func: &Function, w: Writer) -> Result<()> {
if !func.local_list.is_empty() {
let num_local = func.local_list.len().try_into().unwrap();
write!(w, "local ")?;
write_in_order("loc", num_local, w)?;
write!(w, " = ")?;
for (i, t) in func.local_list.iter().enumerate() {
if i != 0 {
write!(w, ", ")?;
}
write!(w, "ZERO_{} ", t)?;
}
}
if func.num_stack != 0 {
write!(w, "local ")?;
write_in_order("reg", func.num_stack, w)?;
write!(w, " ")?;
}
Ok(())
}