diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-03-11 22:02:35 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-11 22:02:35 -0500 |
| commit | e9a038c33bbf171695b08540536f307b9e418173 (patch) | |
| tree | d2ca77448fca354101e96040b83a7f7edf408647 /src/codegen/c.zig | |
| parent | a5cb4ab95e80c4f75356b80251c3628811956b19 (diff) | |
| parent | fc62ff77c3921758624a81970f3098300992ee47 (diff) | |
| download | zig-e9a038c33bbf171695b08540536f307b9e418173.tar.gz zig-e9a038c33bbf171695b08540536f307b9e418173.zip | |
Merge pull request #7934 from Vexu/stage2-cbe
Stage2 cbe: optionals and errors
Diffstat (limited to 'src/codegen/c.zig')
| -rw-r--r-- | src/codegen/c.zig | 272 |
1 files changed, 271 insertions, 1 deletions
diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1a323441d9..af8d2d272d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -32,6 +32,34 @@ pub const CValue = union(enum) { }; pub const CValueMap = std.AutoHashMap(*Inst, CValue); +pub const TypedefMap = std.HashMap(Type, struct { name: []const u8, rendered: []u8 }, Type.hash, Type.eql, std.hash_map.default_max_load_percentage); + +fn formatTypeAsCIdentifier( + data: Type, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + var buffer = [1]u8{0} ** 128; + // We don't care if it gets cut off, it's still more unique than a number + var buf = std.fmt.bufPrint(&buffer, "{}", .{data}) catch &buffer; + + for (buf) |c, i| { + switch (c) { + 0 => return writer.writeAll(buf[0..i]), + 'a'...'z', 'A'...'Z', '_', '$' => {}, + '0'...'9' => if (i == 0) { + buf[i] = '_'; + }, + else => buf[i] = '_', + } + } + return writer.writeAll(buf); +} + +pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) { + return .{ .data = t }; +} /// This data is available when outputting .c code for a Module. /// It is not available when generating .h file. @@ -115,6 +143,7 @@ pub const DeclGen = struct { decl: *Decl, fwd_decl: std.ArrayList(u8), error_msg: ?*Module.ErrorMsg, + typedefs: TypedefMap, fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{ @@ -140,7 +169,7 @@ pub const DeclGen = struct { return writer.print("{d}", .{val.toUnsignedInt()}); }, .Pointer => switch (val.tag()) { - .undef, .zero => try writer.writeAll("0"), + .null_value, .zero => try writer.writeAll("NULL"), .one => try writer.writeAll("1"), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; @@ -201,6 +230,52 @@ pub const DeclGen = struct { } }, .Bool => return writer.print("{}", .{val.toBool()}), + .Optional => { + var opt_buf: Type.Payload.ElemType = undefined; + const child_type = t.optionalChild(&opt_buf); + if (t.isPtrLikeOptional()) { + return dg.renderValue(writer, child_type, val); + } + try writer.writeByte('('); + try dg.renderType(writer, t); + if (val.tag() == .null_value) { + try writer.writeAll("){ .is_null = true }"); + } else { + try writer.writeAll("){ .is_null = false, .payload = "); + try dg.renderValue(writer, child_type, val); + try writer.writeAll(" }"); + } + }, + .ErrorSet => { + const payload = val.castTag(.@"error").?; + // error values will be #defined at the top of the file + return writer.print("zig_error_{s}", .{payload.data.name}); + }, + .ErrorUnion => { + const error_type = t.errorUnionSet(); + const payload_type = t.errorUnionChild(); + const data = val.castTag(.error_union).?.data; + try writer.writeByte('('); + try dg.renderType(writer, t); + try writer.writeAll("){"); + if (val.getError()) |_| { + try writer.writeAll(" .error = "); + try dg.renderValue( + writer, + error_type, + data, + ); + try writer.writeAll(" }"); + } else { + try writer.writeAll(" .payload = "); + try dg.renderValue( + writer, + payload_type, + data, + ); + try writer.writeAll(", .error = 0 }"); + } + }, else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ @tagName(e), }), @@ -299,6 +374,62 @@ pub const DeclGen = struct { try dg.renderType(w, t.elemType()); try w.writeAll(" *"); }, + .Optional => { + var opt_buf: Type.Payload.ElemType = undefined; + const child_type = t.optionalChild(&opt_buf); + if (t.isPtrLikeOptional()) { + return dg.renderType(w, child_type); + } else if (dg.typedefs.get(t)) |some| { + return w.writeAll(some.name); + } + + var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); + defer buffer.deinit(); + const bw = buffer.writer(); + + try bw.writeAll("typedef struct { "); + try dg.renderType(bw, child_type); + try bw.writeAll(" payload; bool is_null; } "); + const name_index = buffer.items.len; + try bw.print("zig_opt_{s}_t;\n", .{typeToCIdentifier(child_type)}); + + const rendered = buffer.toOwnedSlice(); + errdefer dg.typedefs.allocator.free(rendered); + const name = rendered[name_index .. rendered.len - 2]; + + try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1); + try w.writeAll(name); + dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); + }, + .ErrorSet => { + comptime std.debug.assert(Type.initTag(.anyerror).abiSize(std.Target.current) == 2); + try w.writeAll("uint16_t"); + }, + .ErrorUnion => { + if (dg.typedefs.get(t)) |some| { + return w.writeAll(some.name); + } + const child_type = t.errorUnionChild(); + const set_type = t.errorUnionSet(); + + var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); + defer buffer.deinit(); + const bw = buffer.writer(); + + try bw.writeAll("typedef struct { "); + try dg.renderType(bw, child_type); + try bw.writeAll(" payload; uint16_t error; } "); + const name_index = buffer.items.len; + try bw.print("zig_err_union_{s}_{s}_t;\n", .{ typeToCIdentifier(set_type), typeToCIdentifier(child_type) }); + + const rendered = buffer.toOwnedSlice(); + errdefer dg.typedefs.allocator.free(rendered); + const name = rendered[name_index .. rendered.len - 2]; + + try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1); + try w.writeAll(name); + dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); + }, .Null, .Undefined => unreachable, // must be const or comptime else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ @tagName(e), @@ -429,6 +560,21 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "), .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "), .not => try genUnOp(o, inst.castTag(.not).?, "!"), + .is_null => try genIsNull(o, inst.castTag(.is_null).?), + .is_non_null => try genIsNull(o, inst.castTag(.is_non_null).?), + .is_null_ptr => try genIsNull(o, inst.castTag(.is_null_ptr).?), + .is_non_null_ptr => try genIsNull(o, inst.castTag(.is_non_null_ptr).?), + .wrap_optional => try genWrapOptional(o, inst.castTag(.wrap_optional).?), + .optional_payload => try genOptionalPayload(o, inst.castTag(.optional_payload).?), + .optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload_ptr).?), + .is_err => try genIsErr(o, inst.castTag(.is_err).?), + .is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?), + .unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?), + .unwrap_errunion_err => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err).?), + .unwrap_errunion_payload_ptr => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload_ptr).?), + .unwrap_errunion_err_ptr => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err_ptr).?), + .wrap_errunion_payload => try genWrapErrUnionPay(o, inst.castTag(.wrap_errunion_payload).?), + .wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { @@ -802,6 +948,130 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{}); } +fn genIsNull(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const invert_logic = inst.base.tag == .is_non_null or inst.base.tag == .is_non_null_ptr; + const operator = if (invert_logic) "!=" else "=="; + const maybe_deref = if (inst.base.tag == .is_null_ptr or inst.base.tag == .is_non_null_ptr) "[0]" else ""; + const operand = try o.resolveInst(inst.operand); + + const local = try o.allocLocal(Type.initTag(.bool), .Const); + try writer.writeAll(" = ("); + try o.writeCValue(writer, operand); + + if (inst.operand.ty.isPtrLikeOptional()) { + // operand is a regular pointer, test `operand !=/== NULL` + try writer.print("){s} {s} NULL;\n", .{ maybe_deref, operator }); + } else { + try writer.print("){s}.is_null {s} true;\n", .{ maybe_deref, operator }); + } + return local; +} + +fn genOptionalPayload(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + const opt_ty = if (inst.operand.ty.zigTypeTag() == .Pointer) + inst.operand.ty.elemType() + else + inst.operand.ty; + + if (opt_ty.isPtrLikeOptional()) { + // the operand is just a regular pointer, no need to do anything special. + // *?*T -> **T and ?*T -> *T are **T -> **T and *T -> *T in C + return operand; + } + + const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else "."; + const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else ""; + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.print(" = {s}(", .{maybe_addrof}); + try o.writeCValue(writer, operand); + + try writer.print("){s}payload;\n", .{maybe_deref}); + return local; +} + +// *(E!T) -> E NOT *E +fn genUnwrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else "."; + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.writeAll(" = ("); + try o.writeCValue(writer, operand); + + try writer.print("){s}error;\n", .{maybe_deref}); + return local; +} +fn genUnwrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else "."; + const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else ""; + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.print(" = {s}(", .{maybe_addrof}); + try o.writeCValue(writer, operand); + + try writer.print("){s}payload;\n", .{maybe_deref}); + return local; +} + +fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + if (inst.base.ty.isPtrLikeOptional()) { + // the operand is just a regular pointer, no need to do anything special. + return operand; + } + + // .wrap_optional is used to convert non-optionals into optionals so it can never be null. + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.writeAll(" = { .is_null = false, .payload ="); + try o.writeCValue(writer, operand); + try writer.writeAll("};\n"); + return local; +} +fn genWrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.writeAll(" = { .error = "); + try o.writeCValue(writer, operand); + try writer.writeAll(" };\n"); + return local; +} +fn genWrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.writeAll(" = { .error = 0, .payload = "); + try o.writeCValue(writer, operand); + try writer.writeAll(" };\n"); + return local; +} + +fn genIsErr(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const maybe_deref = if (inst.base.tag == .is_err_ptr) "[0]" else ""; + const operand = try o.resolveInst(inst.operand); + + const local = try o.allocLocal(Type.initTag(.bool), .Const); + try writer.writeAll(" = ("); + try o.writeCValue(writer, operand); + try writer.print("){s}.error != 0;\n", .{maybe_deref}); + return local; +} + fn IndentWriter(comptime UnderlyingWriter: type) type { return struct { const Self = @This(); |
