Skip to content

Commit f4851bb

Browse files
authored
Fix the C lowering for RecordLift to be compatible with C++. (bytecodealliance#902)
* Fix the C lowering for `RecordLift` to be compatible with C++. C++ prohibits non-constant struct literal initializers from being implicitly converted to narrower types, so insert explicit casts. This allows the generated code to be compiled as both C and C++. Specifically, this fixes the following error in C++: ``` test.c:40:5: error: non-constant-expression cannot be narrowed from type 'int32_t' (aka 'int') to 'enum_t' (aka 'unsigned char') in initializer list [-Wc++11-narrowing] ``` While debugging this, I also tidied up some redundant parens in `load_ext`. * Add `-Wc++compat` to the test compiler flags. * Fix more C++ compilation errors and compile the tests in C++ mode too. Compile the C tests in C++ too, and fix several compilation errors that this uncovered. * Minor whitespace tidying.
1 parent 53e07c7 commit f4851bb

File tree

7 files changed

+150
-106
lines changed

7 files changed

+150
-106
lines changed

crates/c/src/lib.rs

Lines changed: 86 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ struct C {
2222
return_pointer_area_align: usize,
2323
names: Ns,
2424
needs_string: bool,
25+
needs_union_int32_float: bool,
26+
needs_union_float_int32: bool,
27+
needs_union_int64_double: bool,
28+
needs_union_double_int64: bool,
2529
prim_names: HashSet<String>,
2630
world: String,
2731
sizes: SizeAlign,
@@ -331,7 +335,7 @@ impl WorldGenerator for C {
331335
self.src.h_helpers,
332336
"
333337
// Transfers ownership of `s` into the string `ret`
334-
void {snake}_string_set({snake}_string_t *ret, {c_string_ty} *s);
338+
void {snake}_string_set({snake}_string_t *ret, const {c_string_ty} *s);
335339
336340
// Creates a copy of the input nul-terminate string `s` and
337341
// stores it into the component model string `ret`.
@@ -345,14 +349,14 @@ impl WorldGenerator for C {
345349
uwrite!(
346350
self.src.c_helpers,
347351
"
348-
void {snake}_string_set({snake}_string_t *ret, {c_string_ty} *s) {{
352+
void {snake}_string_set({snake}_string_t *ret, const {c_string_ty} *s) {{
349353
ret->ptr = ({ty}*) s;
350354
ret->len = {strlen};
351355
}}
352356
353357
void {snake}_string_dup({snake}_string_t *ret, const {c_string_ty} *s) {{
354358
ret->len = {strlen};
355-
ret->ptr = cabi_realloc(NULL, 0, {size}, ret->len * {size});
359+
ret->ptr = ({ty}*) cabi_realloc(NULL, 0, {size}, ret->len * {size});
356360
memcpy(ret->ptr, s, ret->len * {size});
357361
}}
358362
@@ -366,6 +370,30 @@ impl WorldGenerator for C {
366370
",
367371
);
368372
}
373+
if self.needs_union_int32_float {
374+
uwriteln!(
375+
self.src.c_helpers,
376+
"\nunion int32_float {{ int32_t a; float b; }};"
377+
);
378+
}
379+
if self.needs_union_float_int32 {
380+
uwriteln!(
381+
self.src.c_helpers,
382+
"\nunion float_int32 {{ float a; int32_t b; }};"
383+
);
384+
}
385+
if self.needs_union_int64_double {
386+
uwriteln!(
387+
self.src.c_helpers,
388+
"\nunion int64_double {{ int64_t a; double b; }};"
389+
);
390+
}
391+
if self.needs_union_double_int64 {
392+
uwriteln!(
393+
self.src.c_helpers,
394+
"\nunion double_int64 {{ double a; int64_t b; }};"
395+
);
396+
}
369397
let version = env!("CARGO_PKG_VERSION");
370398
let mut h_str = wit_bindgen_core::Source::default();
371399

@@ -570,6 +598,51 @@ impl C {
570598
self.type_names.retain(|k, _| live_import_types.contains(k));
571599
self.resources.retain(|k, _| live_import_types.contains(k));
572600
}
601+
602+
fn perform_cast(&mut self, op: &str, cast: &Bitcast) -> String {
603+
match cast {
604+
Bitcast::I32ToF32 | Bitcast::I64ToF32 => {
605+
self.needs_union_int32_float = true;
606+
format!("((union int32_float){{ (int32_t) {} }}).b", op)
607+
}
608+
Bitcast::F32ToI32 | Bitcast::F32ToI64 => {
609+
self.needs_union_float_int32 = true;
610+
format!("((union float_int32){{ {} }}).b", op)
611+
}
612+
Bitcast::I64ToF64 => {
613+
self.needs_union_int64_double = true;
614+
format!("((union int64_double){{ (int64_t) {} }}).b", op)
615+
}
616+
Bitcast::F64ToI64 => {
617+
self.needs_union_double_int64 = true;
618+
format!("((union double_int64){{ {} }}).b", op)
619+
}
620+
Bitcast::I32ToI64 | Bitcast::LToI64 | Bitcast::PToP64 => {
621+
format!("(int64_t) {}", op)
622+
}
623+
Bitcast::I64ToI32 | Bitcast::I64ToL => {
624+
format!("(int32_t) {}", op)
625+
}
626+
// P64 is currently represented as int64_t, so no conversion is needed.
627+
Bitcast::I64ToP64 | Bitcast::P64ToI64 => {
628+
format!("{}", op)
629+
}
630+
Bitcast::P64ToP | Bitcast::I32ToP | Bitcast::LToP => {
631+
format!("(uint8_t *) {}", op)
632+
}
633+
634+
// Cast to uintptr_t to avoid implicit pointer-to-int conversions.
635+
Bitcast::PToI32 | Bitcast::PToL => format!("(uintptr_t) {}", op),
636+
637+
Bitcast::I32ToL | Bitcast::LToI32 | Bitcast::None => op.to_string(),
638+
639+
Bitcast::Sequence(sequence) => {
640+
let [first, second] = &**sequence;
641+
let inner = self.perform_cast(op, first);
642+
self.perform_cast(&inner, second)
643+
}
644+
}
645+
}
573646
}
574647

575648
pub fn imported_types_used_by_exported_interfaces(
@@ -2068,7 +2141,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
20682141
fn load_ext(&mut self, ty: &str, offset: i32, operands: &[String], results: &mut Vec<String>) {
20692142
self.load(ty, offset, operands, results);
20702143
let result = results.pop().unwrap();
2071-
results.push(format!("(int32_t) ({})", result));
2144+
results.push(format!("(int32_t) {}", result));
20722145
}
20732146

20742147
fn store(&mut self, ty: &str, offset: i32, operands: &[String]) {
@@ -2208,7 +2281,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
22082281

22092282
Instruction::Bitcasts { casts } => {
22102283
for (cast, op) in casts.iter().zip(operands) {
2211-
let op = perform_cast(op, cast);
2284+
let op = self.gen.gen.perform_cast(op, cast);
22122285
results.push(op);
22132286
}
22142287
}
@@ -2223,11 +2296,12 @@ impl Bindgen for FunctionBindgen<'_, '_> {
22232296
results.push(format!("({}).{}", op, to_c_ident(&f.name)));
22242297
}
22252298
}
2226-
Instruction::RecordLift { ty, .. } => {
2299+
Instruction::RecordLift { ty, record, .. } => {
22272300
let name = self.gen.gen.type_name(&Type::Id(*ty));
22282301
let mut result = format!("({}) {{\n", name);
2229-
for op in operands {
2230-
uwriteln!(result, "{},", op);
2302+
for (field, op) in record.fields.iter().zip(operands.iter()) {
2303+
let field_ty = self.gen.gen.type_name(&field.ty);
2304+
uwriteln!(result, "({}) {},", field_ty, op);
22312305
}
22322306
result.push_str("}");
22332307
results.push(result);
@@ -2239,11 +2313,12 @@ impl Bindgen for FunctionBindgen<'_, '_> {
22392313
results.push(format!("({}).f{}", op, i));
22402314
}
22412315
}
2242-
Instruction::TupleLift { ty, .. } => {
2316+
Instruction::TupleLift { ty, tuple, .. } => {
22432317
let name = self.gen.gen.type_name(&Type::Id(*ty));
22442318
let mut result = format!("({}) {{\n", name);
2245-
for op in operands {
2246-
uwriteln!(result, "{},", op);
2319+
for (ty, op) in tuple.types.iter().zip(operands.iter()) {
2320+
let ty = self.gen.gen.type_name(&ty);
2321+
uwriteln!(result, "({}) {},", ty, op);
22472322
}
22482323
result.push_str("}");
22492324
results.push(result);
@@ -2943,46 +3018,6 @@ impl Bindgen for FunctionBindgen<'_, '_> {
29433018
}
29443019
}
29453020

2946-
fn perform_cast(op: &str, cast: &Bitcast) -> String {
2947-
match cast {
2948-
Bitcast::I32ToF32 | Bitcast::I64ToF32 => {
2949-
format!("((union {{ int32_t a; float b; }}){{ {} }}).b", op)
2950-
}
2951-
Bitcast::F32ToI32 | Bitcast::F32ToI64 => {
2952-
format!("((union {{ float a; int32_t b; }}){{ {} }}).b", op)
2953-
}
2954-
Bitcast::I64ToF64 => {
2955-
format!("((union {{ int64_t a; double b; }}){{ {} }}).b", op)
2956-
}
2957-
Bitcast::F64ToI64 => {
2958-
format!("((union {{ double a; int64_t b; }}){{ {} }}).b", op)
2959-
}
2960-
Bitcast::I32ToI64 | Bitcast::LToI64 | Bitcast::PToP64 => {
2961-
format!("(int64_t) {}", op)
2962-
}
2963-
Bitcast::I64ToI32 | Bitcast::I64ToL => {
2964-
format!("(int32_t) {}", op)
2965-
}
2966-
// P64 is currently represented as int64_t, so no conversion is needed.
2967-
Bitcast::I64ToP64 | Bitcast::P64ToI64 => {
2968-
format!("{}", op)
2969-
}
2970-
Bitcast::P64ToP | Bitcast::I32ToP | Bitcast::LToP => {
2971-
format!("(uint8_t *) {}", op)
2972-
}
2973-
2974-
// Cast to uintptr_t to avoid implicit pointer-to-int conversions.
2975-
Bitcast::PToI32 | Bitcast::PToL => format!("(uintptr_t) {}", op),
2976-
2977-
Bitcast::I32ToL | Bitcast::LToI32 | Bitcast::None => op.to_string(),
2978-
2979-
Bitcast::Sequence(sequence) => {
2980-
let [first, second] = &**sequence;
2981-
perform_cast(&perform_cast(op, first), second)
2982-
}
2983-
}
2984-
}
2985-
29863021
#[derive(Default, Clone, Copy)]
29873022
enum SourceType {
29883023
#[default]

crates/c/tests/codegen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ fn verify(dir: &Path, name: &str) {
6161
dir.to_str().unwrap(),
6262
"-Wall",
6363
"-Wextra",
64+
"-Wc++-compat",
6465
"-Werror",
6566
"-Wno-unused-parameter",
6667
"-c",

tests/runtime/flavorful/wasm.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,11 @@ void exports_test_flavorful_test_list_typedefs(exports_test_flavorful_test_list_
189189
assert(memcmp(c->ptr[0].ptr, "typedef2", c->ptr[0].len) == 0);
190190
exports_test_flavorful_test_list_typedef3_free(c);
191191

192-
ret0->ptr = malloc(8);
192+
ret0->ptr = (uint8_t *) malloc(8);
193193
ret0->len = 8;
194194
memcpy(ret0->ptr, "typedef3", 8);
195195

196-
ret1->ptr = malloc(sizeof(flavorful_string_t));
196+
ret1->ptr = (flavorful_string_t *) malloc(sizeof(flavorful_string_t));
197197
ret1->len = 1;
198198
flavorful_string_dup(&ret1->ptr[0], "typedef4");
199199
}

tests/runtime/lists/wasm.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ void exports_test_lists_test_list_param4(lists_list_list_string_t *a) {
307307
}
308308

309309
void exports_test_lists_test_list_result(lists_list_u8_t *ret0) {
310-
ret0->ptr = malloc(5);
310+
ret0->ptr = (uint8_t *) malloc(5);
311311
ret0->len = 5;
312312
ret0->ptr[0] = 1;
313313
ret0->ptr[1] = 2;
@@ -322,7 +322,7 @@ void exports_test_lists_test_list_result2(lists_string_t *ret0) {
322322

323323
void exports_test_lists_test_list_result3(lists_list_string_t *ret0) {
324324
ret0->len = 2;
325-
ret0->ptr = malloc(2 * sizeof(lists_string_t));
325+
ret0->ptr = (lists_string_t *) malloc(2 * sizeof(lists_string_t));
326326

327327
lists_string_dup(&ret0->ptr[0], "hello,");
328328
lists_string_dup(&ret0->ptr[1], "world!");

tests/runtime/main.rs

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -207,57 +207,64 @@ fn tests(name: &str, dir_name: &str) -> Result<Vec<PathBuf>> {
207207
let sdk = PathBuf::from(std::env::var_os("WASI_SDK_PATH").expect(
208208
"point the `WASI_SDK_PATH` environment variable to the path of your wasi-sdk",
209209
));
210-
let mut cmd = Command::new(sdk.join("bin/clang"));
211-
let out_wasm = out_dir.join(format!(
212-
"c-{}.wasm",
213-
path.file_stem().and_then(|s| s.to_str()).unwrap()
214-
));
215-
cmd.arg("--sysroot").arg(sdk.join("share/wasi-sysroot"));
216-
cmd.arg(path)
217-
.arg(out_dir.join(format!("{snake}.c")))
218-
.arg(out_dir.join(format!("{snake}_component_type.o")))
219-
.arg("-I")
220-
.arg(&out_dir)
221-
.arg("-Wall")
222-
.arg("-Wextra")
223-
.arg("-Werror")
224-
.arg("-Wno-unused-parameter")
225-
.arg("-mexec-model=reactor")
226-
.arg("-g")
227-
.arg("-o")
228-
.arg(&out_wasm);
229-
println!("{:?}", cmd);
230-
let output = match cmd.output() {
231-
Ok(output) => output,
232-
Err(e) => panic!("failed to spawn compiler: {}", e),
233-
};
234-
235-
if !output.status.success() {
236-
println!("status: {}", output.status);
237-
println!("stdout: ------------------------------------------");
238-
println!("{}", String::from_utf8_lossy(&output.stdout));
239-
println!("stderr: ------------------------------------------");
240-
println!("{}", String::from_utf8_lossy(&output.stderr));
241-
panic!("failed to compile");
242-
}
243-
244-
// Translate the canonical ABI module into a component.
245-
let module = fs::read(&out_wasm).expect("failed to read wasm file");
246-
let component = ComponentEncoder::default()
247-
.module(module.as_slice())
248-
.expect("pull custom sections from module")
249-
.validate(true)
250-
.adapter("wasi_snapshot_preview1", &wasi_adapter)
251-
.expect("adapter failed to get loaded")
252-
.encode()
253-
.expect(&format!(
254-
"module {:?} can be translated to a component",
255-
out_wasm
210+
// Test both C mode and C++ mode.
211+
for compiler in ["bin/clang", "bin/clang++"] {
212+
let mut cmd = Command::new(sdk.join(compiler));
213+
let out_wasm = out_dir.join(format!(
214+
"c-{}.wasm",
215+
path.file_stem().and_then(|s| s.to_str()).unwrap()
256216
));
257-
let component_path = out_wasm.with_extension("component.wasm");
258-
fs::write(&component_path, component).expect("write component to disk");
217+
cmd.arg("--sysroot").arg(sdk.join("share/wasi-sysroot"));
218+
cmd.arg(path)
219+
.arg(out_dir.join(format!("{snake}.c")))
220+
.arg(out_dir.join(format!("{snake}_component_type.o")))
221+
.arg("-I")
222+
.arg(&out_dir)
223+
.arg("-Wall")
224+
.arg("-Wextra")
225+
.arg("-Werror")
226+
.arg("-Wno-unused-parameter")
227+
.arg("-mexec-model=reactor")
228+
.arg("-g")
229+
.arg("-o")
230+
.arg(&out_wasm);
231+
// Disable the warning about compiling a `.c` file in C++ mode.
232+
if compiler.ends_with("++") {
233+
cmd.arg("-Wno-deprecated");
234+
}
235+
println!("{:?}", cmd);
236+
let output = match cmd.output() {
237+
Ok(output) => output,
238+
Err(e) => panic!("failed to spawn compiler: {}", e),
239+
};
240+
241+
if !output.status.success() {
242+
println!("status: {}", output.status);
243+
println!("stdout: ------------------------------------------");
244+
println!("{}", String::from_utf8_lossy(&output.stdout));
245+
println!("stderr: ------------------------------------------");
246+
println!("{}", String::from_utf8_lossy(&output.stderr));
247+
panic!("failed to compile");
248+
}
259249

260-
result.push(component_path);
250+
// Translate the canonical ABI module into a component.
251+
let module = fs::read(&out_wasm).expect("failed to read wasm file");
252+
let component = ComponentEncoder::default()
253+
.module(module.as_slice())
254+
.expect("pull custom sections from module")
255+
.validate(true)
256+
.adapter("wasi_snapshot_preview1", &wasi_adapter)
257+
.expect("adapter failed to get loaded")
258+
.encode()
259+
.expect(&format!(
260+
"module {:?} can be translated to a component",
261+
out_wasm
262+
));
263+
let component_path = out_wasm.with_extension("component.wasm");
264+
fs::write(&component_path, component).expect("write component to disk");
265+
266+
result.push(component_path);
267+
}
261268
}
262269
}
263270

tests/runtime/resource_import_and_export/wasm.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ resource_import_and_export_toplevel_export(resource_import_and_export_own_thing_
1919
exports_test_resource_import_and_export_test_own_thing_t
2020
exports_test_resource_import_and_export_test_constructor_thing(uint32_t v) {
2121
exports_test_resource_import_and_export_test_thing_t *val =
22+
(exports_test_resource_import_and_export_test_thing_t *)
2223
malloc(sizeof(exports_test_resource_import_and_export_test_thing_t));
2324
assert(val != NULL);
2425
val->thing = test_resource_import_and_export_test_constructor_thing(v + 1);

tests/runtime/strings/wasm_utf16.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
char16_t STR_BUFFER[500];
88

9-
void assert_str(strings_string_t* str, char16_t* expected) {
9+
void assert_str(strings_string_t* str, const char16_t* expected) {
1010
size_t expected_len = 0;
1111
while (expected[expected_len])
1212
expected_len++;
@@ -32,7 +32,7 @@ void strings_return_empty(strings_string_t *ret) {
3232
void strings_roundtrip(strings_string_t *str, strings_string_t *ret) {
3333
assert(str->len > 0);
3434
ret->len = str->len;
35-
ret->ptr = malloc(ret->len * 2);
35+
ret->ptr = (uint16_t *) malloc(ret->len * 2);
3636
memcpy(ret->ptr, str->ptr, 2 * ret->len);
3737
strings_string_free(str);
3838
}

0 commit comments

Comments
 (0)