diff options
| author | drew <reserveblue@protonmail.com> | 2021-12-30 12:19:12 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-12-30 15:19:12 -0500 |
| commit | 2f53406ad821c6fd8239551d3b812cd4100929bd (patch) | |
| tree | 3ef2ae54516af3842c2c144879c5b7ac253b0913 | |
| parent | 726ee671befd06b18fec90a01dabf2eca96b2d02 (diff) | |
| download | zig-2f53406ad821c6fd8239551d3b812cd4100929bd.tar.gz zig-2f53406ad821c6fd8239551d3b812cd4100929bd.zip | |
CBE; implement airLoad and airStore for arrays (#10452)
Effectively a small continuation of #10152
This allows the for.zig behavior tests to pass. Unfortunately to fully test everything I had to move a lot of behavior tests from array.zig; most of them now pass (sorry @rainbowbismuth!)
I'm also conflicted on how I store constants into arrays because it's kind of stupid; array's can't be re-initialized using the same syntax, so instead of initializing each element, a new array is made which is copied into the destination. This also required that renderValue can't emit string literals for byte arrays given that they need to always have an extra byte for the NULL terminator, meaning that strings are no longer grep-able in the output.
| -rw-r--r-- | src/codegen/c.zig | 85 | ||||
| -rw-r--r-- | test/behavior.zig | 5 | ||||
| -rw-r--r-- | test/behavior/array.zig | 32 | ||||
| -rw-r--r-- | test/behavior/array_llvm.zig | 35 |
4 files changed, 99 insertions, 58 deletions
diff --git a/src/codegen/c.zig b/src/codegen/c.zig index b23b937e05..191be36494 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -385,11 +385,6 @@ pub const DeclGen = struct { // First try specific tag representations for more efficiency. switch (val.tag()) { .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), - .bytes => { - const bytes = val.castTag(.bytes).?.data; - // TODO: make our own C string escape instead of using std.zig.fmtEscapes - try writer.print("\"{}\"", .{std.zig.fmtEscapes(bytes)}); - }, else => { // Fall back to generic implementation. var arena = std.heap.ArenaAllocator.init(dg.module.gpa); @@ -1449,14 +1444,18 @@ fn airArg(f: *Function) CValue { fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { const ty_op = f.air.instructions.items(.data)[inst].ty_op; const is_volatile = f.air.typeOf(ty_op.operand).isVolatilePtr(); + if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; + const inst_ty = f.air.typeOfIndex(inst); - if (inst_ty.zigTypeTag() == .Array) - return f.fail("TODO: C backend: implement airLoad for arrays", .{}); + const is_array = inst_ty.zigTypeTag() == .Array; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); - const local = try f.allocLocal(inst_ty, .Const); + + // We need to separately initialize arrays with a memcpy so they must be mutable. + const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const); + switch (operand) { .local_ref => |i| { const wrapped: CValue = .{ .local = i }; @@ -1471,9 +1470,23 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { try writer.writeAll(";\n"); }, else => { - try writer.writeAll(" = *"); - try f.writeCValue(writer, operand); - try writer.writeAll(";\n"); + if (is_array) { + // Insert a memcpy to initialize this array. The source operand is always a pointer + // and thus we only need to know size/type information from the local type/dest. + try writer.writeAll(";"); + try f.object.indent_writer.insertNewline(); + try writer.writeAll("memcpy("); + try f.writeCValue(writer, local); + try writer.writeAll(", "); + try f.writeCValue(writer, operand); + try writer.writeAll(", sizeof("); + try f.writeCValue(writer, local); + try writer.writeAll("));\n"); + } else { + try writer.writeAll(" = *"); + try f.writeCValue(writer, operand); + try writer.writeAll(";\n"); + } }, } return local; @@ -1580,7 +1593,7 @@ fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue { return local; } -fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_type: Type) !CValue { +fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_child_type: Type) !CValue { const is_debug_build = f.object.dg.module.optimizeMode() == .Debug; if (!is_debug_build) return CValue.none; @@ -1604,7 +1617,7 @@ fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_type: Type) !CValue { try writer.writeAll("));\n"); }, else => { - const indirection = if (dest_type.childType().zigTypeTag() == .Array) "" else "*"; + const indirection = if (dest_child_type.zigTypeTag() == .Array) "" else "*"; try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr); @@ -1621,18 +1634,14 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const dest_ptr = try f.resolveInst(bin_op.lhs); const src_val = try f.resolveInst(bin_op.rhs); - const lhs_type = f.air.typeOf(bin_op.lhs); + const lhs_child_type = f.air.typeOf(bin_op.lhs).childType(); // TODO Sema should emit a different instruction when the store should // possibly do the safety 0xaa bytes for undefined. const src_val_is_undefined = if (f.air.value(bin_op.rhs)) |v| v.isUndefDeep() else false; if (src_val_is_undefined) - return try airStoreUndefined(f, dest_ptr, lhs_type); - - // Don't check this for airStoreUndefined as that will work for arrays already - if (lhs_type.childType().zigTypeTag() == .Array) - return f.fail("TODO: C backend: implement airStore for arrays", .{}); + return try airStoreUndefined(f, dest_ptr, lhs_child_type); const writer = f.object.writer(); switch (dest_ptr) { @@ -1651,11 +1660,39 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { try writer.writeAll(";\n"); }, else => { - try writer.writeAll("*"); - try f.writeCValue(writer, dest_ptr); - try writer.writeAll(" = "); - try f.writeCValue(writer, src_val); - try writer.writeAll(";\n"); + if (lhs_child_type.zigTypeTag() == .Array) { + // For this memcpy to safely work we need the rhs to have the same + // underlying type as the lhs (i.e. they must both be arrays of the same underlying type). + const rhs_type = f.air.typeOf(bin_op.rhs); + assert(rhs_type.eql(lhs_child_type)); + + // If the source is a constant, writeCValue will emit a brace initialization + // so work around this by initializing into new local. + // TODO this should be done by manually initializing elements of the dest array + const array_src = if (src_val == .constant) blk: { + const new_local = try f.allocLocal(rhs_type, .Const); + try writer.writeAll(" = "); + try f.writeCValue(writer, src_val); + try writer.writeAll(";"); + try f.object.indent_writer.insertNewline(); + + break :blk new_local; + } else src_val; + + try writer.writeAll("memcpy("); + try f.writeCValue(writer, dest_ptr); + try writer.writeAll(", "); + try f.writeCValue(writer, array_src); + try writer.writeAll(", sizeof("); + try f.writeCValue(writer, array_src); + try writer.writeAll("));\n"); + } else { + try writer.writeAll("*"); + try f.writeCValue(writer, dest_ptr); + try writer.writeAll(" = "); + try f.writeCValue(writer, src_val); + try writer.writeAll(";\n"); + } }, } return CValue.none; diff --git a/test/behavior.zig b/test/behavior.zig index 74d7b1f73c..03de899bf5 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -25,6 +25,7 @@ test { // Tests that pass for stage1, stage2 and the C backend, but not for the wasm backend if (!builtin.zig_is_stage2 or builtin.stage2_arch != .wasm32) { _ = @import("behavior/align.zig"); + _ = @import("behavior/array.zig"); _ = @import("behavior/bool.zig"); _ = @import("behavior/bugs/704.zig"); _ = @import("behavior/bugs/2692.zig"); @@ -41,6 +42,7 @@ test { _ = @import("behavior/defer.zig"); _ = @import("behavior/error.zig"); _ = @import("behavior/fn_in_struct_in_comptime.zig"); + _ = @import("behavior/for.zig"); _ = @import("behavior/generics.zig"); _ = @import("behavior/if.zig"); _ = @import("behavior/incomplete_struct_param_tld.zig"); @@ -63,7 +65,7 @@ test { // Tests that pass for stage1 and stage2 but not the C backend and wasm backend. _ = @import("behavior/align_llvm.zig"); _ = @import("behavior/alignof.zig"); - _ = @import("behavior/array.zig"); + _ = @import("behavior/array_llvm.zig"); _ = @import("behavior/atomics.zig"); _ = @import("behavior/basic_llvm.zig"); _ = @import("behavior/bugs/394.zig"); @@ -85,7 +87,6 @@ test { _ = @import("behavior/eval.zig"); _ = @import("behavior/floatop.zig"); _ = @import("behavior/fn.zig"); - _ = @import("behavior/for.zig"); _ = @import("behavior/generics_llvm.zig"); _ = @import("behavior/math.zig"); _ = @import("behavior/maximum_minimum.zig"); diff --git a/test/behavior/array.zig b/test/behavior/array.zig index c9c376be87..cd74640bea 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -124,21 +124,6 @@ test "nested arrays" { } } -var s_array: [8]Sub = undefined; -const Sub = struct { b: u8 }; -const Str = struct { a: []Sub }; -test "set global var array via slice embedded in struct" { - var s = Str{ .a = s_array[0..] }; - - s.a[0].b = 1; - s.a[1].b = 2; - s.a[2].b = 3; - - try expect(s_array[0].b == 1); - try expect(s_array[1].b == 2); - try expect(s_array[2].b == 3); -} - test "implicit comptime in array type size" { var arr: [plusOne(10)]bool = undefined; try expect(arr.len == 11); @@ -148,23 +133,6 @@ fn plusOne(x: u32) u32 { return x + 1; } -test "read/write through global variable array of struct fields initialized via array mult" { - const S = struct { - fn doTheTest() !void { - try expect(storage[0].term == 1); - storage[0] = MyStruct{ .term = 123 }; - try expect(storage[0].term == 123); - } - - pub const MyStruct = struct { - term: usize, - }; - - var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1; - }; - try S.doTheTest(); -} - test "single-item pointer to array indexing and slicing" { try testSingleItemPtrArrayIndexSlice(); comptime try testSingleItemPtrArrayIndexSlice(); diff --git a/test/behavior/array_llvm.zig b/test/behavior/array_llvm.zig new file mode 100644 index 0000000000..8e65045210 --- /dev/null +++ b/test/behavior/array_llvm.zig @@ -0,0 +1,35 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; + +var s_array: [8]Sub = undefined; +const Sub = struct { b: u8 }; +const Str = struct { a: []Sub }; +test "set global var array via slice embedded in struct" { + var s = Str{ .a = s_array[0..] }; + + s.a[0].b = 1; + s.a[1].b = 2; + s.a[2].b = 3; + + try expect(s_array[0].b == 1); + try expect(s_array[1].b == 2); + try expect(s_array[2].b == 3); +} + +test "read/write through global variable array of struct fields initialized via array mult" { + const S = struct { + fn doTheTest() !void { + try expect(storage[0].term == 1); + storage[0] = MyStruct{ .term = 123 }; + try expect(storage[0].term == 123); + } + + pub const MyStruct = struct { + term: usize, + }; + + var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1; + }; + try S.doTheTest(); +} |
