use heck::ShoutySnakeCase; use witx::*; pub fn to_c_header(doc: &Document, inputs_str: &str) -> String { let mut ret = String::new(); ret.push_str(&format!( r#"/** * THIS FILE IS AUTO-GENERATED from the following files: * {} * * @file * This file describes the [WASI] interface, consisting of functions, types, * and defined values (macros). * * The interface described here is greatly inspired by [CloudABI]'s clean, * thoughtfully-designed, capability-oriented, POSIX-style API. * * [CloudABI]: https://github.com/NuxiNL/cloudlibc * [WASI]: https://github.com/WebAssembly/WASI/ */ #ifndef __wasi_api_h #define __wasi_api_h #ifndef __wasi__ #error is only supported on WASI platforms. #endif #ifndef __wasm32__ #error only supports wasm32; doesn't yet support wasm64 #endif #include #include _Static_assert(_Alignof(int8_t) == 1, "non-wasi data layout"); _Static_assert(_Alignof(uint8_t) == 1, "non-wasi data layout"); _Static_assert(_Alignof(int16_t) == 2, "non-wasi data layout"); _Static_assert(_Alignof(uint16_t) == 2, "non-wasi data layout"); _Static_assert(_Alignof(int32_t) == 4, "non-wasi data layout"); _Static_assert(_Alignof(uint32_t) == 4, "non-wasi data layout"); _Static_assert(_Alignof(int64_t) == 8, "non-wasi data layout"); _Static_assert(_Alignof(uint64_t) == 8, "non-wasi data layout"); _Static_assert(_Alignof(void*) == 4, "non-wasi data layout"); #ifdef __cplusplus extern "C" {{ #endif // TODO: Encoding this in witx. #define __WASI_DIRCOOKIE_START (UINT64_C(0)) "#, inputs_str, )); for nt in doc.typenames() { print_datatype(&mut ret, &*nt); } for m in doc.modules() { print_module(&mut ret, &m); } ret.push_str( r#"#ifdef __cplusplus } #endif #endif "#, ); ret } fn print_datatype(ret: &mut String, nt: &NamedType) { if !nt.docs.is_empty() { ret.push_str("/**\n"); for line in nt.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } ret.push_str(" */\n"); } match &nt.tref { TypeRef::Value(v) => match &**v { Type::Enum(e) => print_enum(ret, &nt.name, e), Type::Int(i) => print_int(ret, &nt.name, i), Type::Flags(f) => print_flags(ret, &nt.name, f), Type::Struct(s) => print_struct(ret, &nt.name, s), Type::Union(u) => print_union(ret, &nt.name, u), Type::Handle(h) => print_handle(ret, &nt.name, h), Type::Builtin { .. } | Type::Array { .. } | Type::Pointer { .. } | Type::ConstPointer { .. } => print_alias(ret, &nt.name, &nt.tref), }, TypeRef::Name(_) => print_alias(ret, &nt.name, &nt.tref), } } fn print_alias(ret: &mut String, name: &Id, dest: &TypeRef) { match &*dest.type_() { Type::Array(_) => { // Don't emit arrays as top-level types; instead we special-case // them in places like parameter lists so that we can pass them // as pointer and length pairs. } _ => { if name.as_str() == "size" { // Special-case "size" as "__SIZE_TYPE__" -- TODO: Encode this in witx. ret.push_str(&format!( "typedef __SIZE_TYPE__ __wasi_{}_t;\n", ident_name(name) )); } else { ret.push_str(&format!( "typedef {} __wasi_{}_t;\n", typeref_name(dest), ident_name(name) )); } ret.push_str("\n"); ret.push_str(&format!( "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n", ident_name(name), dest.mem_size_align().size )); ret.push_str(&format!( "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n", ident_name(name), dest.mem_size_align().align )); ret.push_str("\n"); } } } fn print_enum(ret: &mut String, name: &Id, e: &EnumDatatype) { ret.push_str(&format!( "typedef {} __wasi_{}_t;\n", intrepr_name(e.repr), ident_name(name) )); ret.push_str("\n"); for (index, variant) in e.variants.iter().enumerate() { if !variant.docs.is_empty() { ret.push_str("/**\n"); for line in variant.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } ret.push_str(" */\n"); } ret.push_str(&format!( "#define __WASI_{}_{} ({}({}))\n", ident_name(&name).to_shouty_snake_case(), ident_name(&variant.name).to_shouty_snake_case(), intrepr_const(e.repr), index )); ret.push_str("\n"); } ret.push_str(&format!( "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n", ident_name(name), e.repr.mem_size() )); ret.push_str(&format!( "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n", ident_name(name), e.repr.mem_align() )); ret.push_str("\n"); } fn print_int(ret: &mut String, name: &Id, i: &IntDatatype) { ret.push_str(&format!( "typedef {} __wasi_{}_t;\n", intrepr_name(i.repr), ident_name(name) )); ret.push_str("\n"); for (index, const_) in i.consts.iter().enumerate() { if !const_.docs.is_empty() { ret.push_str("/**\n"); for line in const_.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } ret.push_str(" */\n"); } ret.push_str(&format!( "#define __WASI_{}_{} ({}({}))\n", ident_name(&name).to_shouty_snake_case(), ident_name(&const_.name).to_shouty_snake_case(), intrepr_const(i.repr), index )); ret.push_str("\n"); } ret.push_str(&format!( "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n", ident_name(name), i.repr.mem_size() )); ret.push_str(&format!( "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n", ident_name(name), i.repr.mem_align() )); ret.push_str("\n"); } fn print_flags(ret: &mut String, name: &Id, f: &FlagsDatatype) { ret.push_str(&format!( "typedef {} __wasi_{}_t;\n", intrepr_name(f.repr), ident_name(name) )); ret.push_str("\n"); for (index, flag) in f.flags.iter().enumerate() { if !flag.docs.is_empty() { ret.push_str("/**\n"); for line in flag.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } ret.push_str(" */\n"); } ret.push_str(&format!( "#define __WASI_{}_{} ({}({}))\n", ident_name(name).to_shouty_snake_case(), ident_name(&flag.name).to_shouty_snake_case(), intrepr_const(f.repr), 1u128 << index )); ret.push_str("\n"); } ret.push_str(&format!( "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n", ident_name(name), f.repr.mem_size(), )); ret.push_str(&format!( "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n", ident_name(name), f.repr.mem_align(), )); ret.push_str("\n"); } fn print_struct(ret: &mut String, name: &Id, s: &StructDatatype) { ret.push_str(&format!( "typedef struct __wasi_{}_t {{\n", ident_name(name) )); for member in &s.members { if !member.docs.is_empty() { ret.push_str(" /**\n"); for line in member.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } ret.push_str(" */\n"); } ret.push_str(&format!( " {} {};\n", typeref_name(&member.tref), ident_name(&member.name) )); ret.push_str("\n"); } ret.push_str(&format!("}} __wasi_{}_t;\n", ident_name(name))); ret.push_str("\n"); ret.push_str(&format!( "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n", ident_name(name), s.mem_size() )); ret.push_str(&format!( "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n", ident_name(name), s.mem_align() )); for layout in s.member_layout() { ret.push_str(&format!( "_Static_assert(offsetof(__wasi_{}_t, {}) == {}, \"witx calculated offset\");\n", ident_name(name), ident_name(&layout.member.name), layout.offset )); } ret.push_str("\n"); } fn print_union(ret: &mut String, name: &Id, u: &UnionDatatype) { ret.push_str(&format!( "typedef union __wasi_{}_u_t {{\n", ident_name(name) )); for variant in &u.variants { if let Some(ref tref) = variant.tref { if !variant.docs.is_empty() { ret.push_str(" /**\n"); for line in variant.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } ret.push_str(" */\n"); } ret.push_str(&format!( " {} {};\n", typeref_name(tref), ident_name(&variant.name) )); } } ret.push_str(&format!("}} __wasi_{}_u_t;\n", ident_name(name))); ret.push_str(&format!( "typedef struct __wasi_{}_t {{\n", ident_name(name) )); ret.push_str(&format!(" {} tag;\n", namedtype_name(&u.tag))); ret.push_str(&format!(" __wasi_{}_u_t u;\n", ident_name(name))); ret.push_str(&format!("}} __wasi_{}_t;\n", ident_name(name))); ret.push_str("\n"); ret.push_str(&format!( "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n", ident_name(name), u.mem_size() )); ret.push_str(&format!( "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n", ident_name(name), u.mem_align() )); let l = u.union_layout(); ret.push_str(&format!( "_Static_assert(offsetof(__wasi_{}_t, u) == {}, \"witx calculated union offset\");\n", ident_name(name), l.contents_offset, )); ret.push_str(&format!( "_Static_assert(sizeof(__wasi_{}_u_t) == {}, \"witx calculated union size\");\n", ident_name(name), l.contents_size, )); ret.push_str(&format!( "_Static_assert(_Alignof(__wasi_{}_u_t) == {}, \"witx calculated union align\");\n", ident_name(name), l.contents_align, )); ret.push_str("\n"); } fn print_handle(ret: &mut String, name: &Id, h: &HandleDatatype) { ret.push_str(&format!("typedef int __wasi_{}_t;", ident_name(name))); ret.push_str(&format!( "_Static_assert(sizeof(__wasi_{}_t) == {}, \"witx calculated size\");\n", ident_name(name), h.mem_size() )); ret.push_str(&format!( "_Static_assert(_Alignof(__wasi_{}_t) == {}, \"witx calculated align\");\n", ident_name(name), h.mem_align() )); ret.push_str("\n"); } fn print_module(ret: &mut String, m: &Module) { ret.push_str("/**\n"); ret.push_str(&format!(" * @defgroup {}\n", ident_name(&m.name),)); for line in m.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } ret.push_str(" * @{\n"); ret.push_str(" */\n"); ret.push_str("\n"); for func in m.funcs() { print_func(ret, &func, &m.name); } ret.push_str("/** @} */\n"); ret.push_str("\n"); } fn print_func(ret: &mut String, func: &InterfaceFunc, module_name: &Id) { if !func.docs.is_empty() { ret.push_str("/**\n"); for line in func.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } if !func.results.is_empty() { let first_result = &func.results[0]; if !first_result.docs.is_empty() { ret.push_str(" * @return\n"); for line in first_result.docs.lines() { ret.push_str(&format!(" * {}", line)); } } } ret.push_str(" */\n"); } if func.results.is_empty() { // Special-case "proc_exit" as _Noreturn -- TODO: Encode this in witx. if func.name.as_str() == "proc_exit" { ret.push_str("_Noreturn "); } ret.push_str("void "); } else { let first_result = &func.results[0]; ret.push_str(&format!("{} ", typeref_name(&first_result.tref))); } ret.push_str(&format!("__wasi_{}(\n", ident_name(&func.name))); if func.params.is_empty() && func.results.len() <= 1 { ret.push_str(" void\n"); } for (index, param) in func.params.iter().enumerate() { if !param.docs.is_empty() { ret.push_str(" /**\n"); for line in param.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } ret.push_str(" */\n"); } add_params(ret, &ident_name(¶m.name), ¶m.tref); ret.push_str(&format!( "{}\n", if index + 1 < func.params.len() || func.results.len() > 1 { ",\n" } else { "" } )); } for (index, result) in func.results.iter().enumerate() { if index == 0 { // The first result is returned by value above. continue; } if !result.docs.is_empty() { ret.push_str(" /**\n"); for line in result.docs.lines() { ret.push_str(&format!(" * {}\n", line)); } ret.push_str(" */\n"); } ret.push_str(&format!( " {} *{}{}\n", typeref_name(&result.tref), ident_name(&result.name), if index + 1 < func.results.len() { "," } else { "" } )); } ret.push_str(") __attribute__((\n"); ret.push_str(&format!( " __import_module__(\"{}\"),\n", ident_name(module_name) )); ret.push_str(&format!( " __import_name__(\"{}\")", ident_name(&func.name) )); if !func.results.is_empty() { ret.push_str(",\n __warn_unused_result__\n"); } ret.push_str("));\n"); ret.push_str("\n"); } fn add_params(ret: &mut String, name: &str, tref: &TypeRef) { match &*tref.type_() { Type::Builtin(BuiltinType::String) => { ret.push_str(&format!(" const char *{},\n", name)); ret.push_str("\n"); ret.push_str(" /**\n"); ret.push_str(&format!( " * The length of the buffer pointed to by `{}`.\n", name, )); ret.push_str(" */\n"); ret.push_str(&format!(" size_t {}_len", name)); } Type::Array(element) => { ret.push_str(&format!( " const {} *{},\n", typeref_name(&element), name )); ret.push_str("\n"); ret.push_str(" /**\n"); ret.push_str(&format!( " * The length of the array pointed to by `{}`.\n", name, )); ret.push_str(" */\n"); ret.push_str(&format!(" size_t {}_len", name)); } _ => { ret.push_str(&format!(" {} {}", typeref_name(tref), name)); } } } fn ident_name(i: &Id) -> String { i.as_str().to_string() } fn builtin_type_name(b: BuiltinType) -> &'static str { match b { BuiltinType::String | BuiltinType::Char8 => { panic!("no type name for string or char8 builtins") } BuiltinType::USize => "size_t", BuiltinType::U8 => "uint8_t", BuiltinType::U16 => "uint16_t", BuiltinType::U32 => "uint32_t", BuiltinType::U64 => "uint64_t", BuiltinType::S8 => "int8_t", BuiltinType::S16 => "int16_t", BuiltinType::S32 => "int32_t", BuiltinType::S64 => "int64_t", BuiltinType::F32 => "float", BuiltinType::F64 => "double", } } fn typeref_name(tref: &TypeRef) -> String { match &*tref.type_() { Type::Builtin(BuiltinType::String) | Type::Builtin(BuiltinType::Char8) | Type::Array(_) => { panic!("unsupported grammar: cannot construct name of string or array",) } _ => {} } match tref { TypeRef::Name(named_type) => namedtype_name(&named_type), TypeRef::Value(anon_type) => match &**anon_type { Type::Array(_) => unreachable!("arrays excluded above"), Type::Builtin(b) => builtin_type_name(*b).to_string(), Type::Pointer(p) => format!("{} *", typeref_name(&*p)), Type::ConstPointer(p) => format!("const {} *", typeref_name(&*p)), Type::Int(i) => format!("{}", intrepr_name(i.repr)), Type::Struct { .. } | Type::Union { .. } | Type::Enum { .. } | Type::Flags { .. } | Type::Handle { .. } => unreachable!( "wasi should not have anonymous structs, unions, enums, flags, handles" ), }, } } fn namedtype_name(named_type: &NamedType) -> String { match &*named_type.type_() { Type::Pointer(p) => format!("{} *", typeref_name(&*p)), Type::ConstPointer(p) => format!("const {} *", typeref_name(&*p)), Type::Array(_) => unreachable!("arrays excluded above"), _ => format!("__wasi_{}_t", named_type.name.as_str()), } } fn intrepr_name(i: IntRepr) -> &'static str { match i { IntRepr::U8 => "uint8_t", IntRepr::U16 => "uint16_t", IntRepr::U32 => "uint32_t", IntRepr::U64 => "uint64_t", } } fn intrepr_const(i: IntRepr) -> &'static str { match i { IntRepr::U8 => "UINT8_C", IntRepr::U16 => "UINT16_C", IntRepr::U32 => "UINT32_C", IntRepr::U64 => "UINT64_C", } }