From 40f0275e7cb7f3f4b2c382e5b457e714080c4cf2 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Fri, 13 Nov 2020 00:50:02 +1100 Subject: Implement emit-h --- src/codegen/c.zig | 130 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 47 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index e562ce320e..425663ad9c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -2,6 +2,7 @@ const std = @import("std"); const link = @import("../link.zig"); const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); const Inst = @import("../ir.zig").Inst; const Value = @import("../value.zig").Value; @@ -19,7 +20,7 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { return allocator.dupe(u8, name); } -fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void { +fn renderType(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, T: Type) !void { switch (T.zigTypeTag()) { .NoReturn => { try writer.writeAll("zig_noreturn void"); @@ -27,16 +28,16 @@ fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void { .Void => try writer.writeAll("void"), .Int => { if (T.tag() == .u8) { - ctx.file.need_stdint = true; + header.need_stdint = true; try writer.writeAll("uint8_t"); } else if (T.tag() == .usize) { - ctx.file.need_stddef = true; + header.need_stddef = true; try writer.writeAll("size_t"); } else { - return ctx.file.fail(ctx.decl.src(), "TODO implement int types", .{}); + return ctx.fail(ctx.decl.src(), "TODO implement int types", .{}); } }, - else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement type {}", .{e}), + else => |e| return ctx.fail(ctx.decl.src(), "TODO implement type {}", .{e}), } } @@ -47,13 +48,13 @@ fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Va return writer.print("{}", .{val.toSignedInt()}); return writer.print("{}", .{val.toUnsignedInt()}); }, - else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement value {}", .{e}), + else => |e| return ctx.fail(ctx.decl.src(), "TODO implement value {}", .{e}), } } -fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { +fn renderFunctionSignature(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; - try renderType(ctx, writer, tv.ty.fnReturnType()); + try renderType(ctx, header, writer, tv.ty.fnReturnType()); // Use the child allocator directly, as we know the name can be freed before // the rest of the arena. const name = try map(ctx.arena.child_allocator, mem.spanZ(decl.name)); @@ -68,7 +69,7 @@ fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl if (index > 0) { try writer.writeAll(", "); } - try renderType(ctx, writer, tv.ty.fnParamType(index)); + try renderType(ctx, header, writer, tv.ty.fnParamType(index)); try writer.print(" arg{}", .{index}); } } @@ -83,6 +84,34 @@ pub fn generate(file: *C, decl: *Decl) !void { } } +pub fn generateHeader( + arena: *std.heap.ArenaAllocator, + module: *Module, + header: *C.Header, + decl: *Decl, +) error{ AnalysisFail, OutOfMemory }!void { + switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { + .Fn => { + var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); + defer inst_map.deinit(); + var ctx = Context{ + .decl = decl, + .arena = arena, + .inst_map = &inst_map, + }; + const writer = header.buf.writer(); + renderFunctionSignature(&ctx, header, writer, decl) catch |err| { + if (err == error.AnalysisFail) { + try module.failed_decls.put(module.gpa, decl, ctx.error_msg); + } + return err; + }; + try writer.writeAll(";\n"); + }, + else => {}, + } +} + fn genArray(file: *C, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; // TODO: prevent inline asm constants from being emitted @@ -102,12 +131,12 @@ fn genArray(file: *C, decl: *Decl) !void { } const Context = struct { - file: *C, decl: *Decl, inst_map: *std.AutoHashMap(*Inst, []u8), arena: *std.heap.ArenaAllocator, argdex: usize = 0, unnamed_index: usize = 0, + error_msg: *Compilation.ErrorMsg = undefined, fn resolveInst(self: *Context, inst: *Inst) ![]u8 { if (inst.cast(Inst.Constant)) |const_inst| { @@ -127,6 +156,11 @@ const Context = struct { return val; } + fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { + self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args); + return error.AnalysisFail; + } + fn deinit(self: *Context) void { self.* = undefined; } @@ -141,14 +175,16 @@ fn genFn(file: *C, decl: *Decl) !void { var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); defer inst_map.deinit(); var ctx = Context{ - .file = file, .decl = decl, .arena = &arena, .inst_map = &inst_map, }; - defer ctx.deinit(); + defer { + file.error_msg = ctx.error_msg; + ctx.deinit(); + } - try renderFunctionSignature(&ctx, writer, decl); + try renderFunctionSignature(&ctx, &file.header, writer, decl); try writer.writeAll(" {"); @@ -158,18 +194,18 @@ fn genFn(file: *C, decl: *Decl) !void { try writer.writeAll("\n"); for (instructions) |inst| { if (switch (inst.tag) { - .assembly => try genAsm(&ctx, inst.castTag(.assembly).?), - .call => try genCall(&ctx, inst.castTag(.call).?), - .add => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "+"), - .sub => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "-"), + .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), + .call => try genCall(&ctx, file, inst.castTag(.call).?), + .add => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "+"), + .sub => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "-"), .ret => try genRet(&ctx, inst.castTag(.ret).?), - .retvoid => try genRetVoid(&ctx), + .retvoid => try genRetVoid(file), .arg => try genArg(&ctx), .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?), - .unreach => try genUnreach(&ctx, inst.castTag(.unreach).?), - .intcast => try genIntCast(&ctx, inst.castTag(.intcast).?), - else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}), + .unreach => try genUnreach(file, inst.castTag(.unreach).?), + .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), + else => |e| return ctx.fail(decl.src(), "TODO implement C codegen for {}", .{e}), }) |name| { try ctx.inst_map.putNoClobber(inst, name); } @@ -185,46 +221,46 @@ fn genArg(ctx: *Context) !?[]u8 { return name; } -fn genRetVoid(ctx: *Context) !?[]u8 { - try ctx.file.main.writer().print(indentation ++ "return;\n", .{}); +fn genRetVoid(file: *C) !?[]u8 { + try file.main.writer().print(indentation ++ "return;\n", .{}); return null; } fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 { - return ctx.file.fail(ctx.decl.src(), "TODO return", .{}); + return ctx.fail(ctx.decl.src(), "TODO return", .{}); } -fn genIntCast(ctx: *Context, inst: *Inst.UnOp) !?[]u8 { +fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { if (inst.base.isUnused()) return null; const op = inst.operand; - const writer = ctx.file.main.writer(); + const writer = file.main.writer(); const name = try ctx.name(); const from = try ctx.resolveInst(inst.operand); try writer.writeAll(indentation ++ "const "); - try renderType(ctx, writer, inst.base.ty); + try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print(" {} = (", .{name}); - try renderType(ctx, writer, inst.base.ty); + try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print("){};\n", .{from}); return name; } -fn genBinOp(ctx: *Context, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { +fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { if (inst.base.isUnused()) return null; const lhs = ctx.resolveInst(inst.lhs); const rhs = ctx.resolveInst(inst.rhs); - const writer = ctx.file.main.writer(); + const writer = file.main.writer(); const name = try ctx.name(); try writer.writeAll(indentation ++ "const "); - try renderType(ctx, writer, inst.base.ty); + try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs }); return name; } -fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { - const writer = ctx.file.main.writer(); - const header = ctx.file.header.writer(); +fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { + const writer = file.main.writer(); + const header = file.header.buf.writer(); try writer.writeAll(indentation); if (inst.func.castTag(.constant)) |func_inst| { if (func_inst.val.cast(Value.Payload.Function)) |func_val| { @@ -235,9 +271,9 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { try writer.print("(void)", .{}); } const tname = mem.spanZ(target.name); - if (ctx.file.called.get(tname) == null) { - try ctx.file.called.put(tname, void{}); - try renderFunctionSignature(ctx, header, target); + if (file.called.get(tname) == null) { + try file.called.put(tname, void{}); + try renderFunctionSignature(ctx, &file.header, header, target); try header.writeAll(";\n"); } try writer.print("{}(", .{tname}); @@ -256,10 +292,10 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { } try writer.writeAll(");\n"); } else { - return ctx.file.fail(ctx.decl.src(), "TODO non-function call target?", .{}); + return ctx.fail(ctx.decl.src(), "TODO non-function call target?", .{}); } } else { - return ctx.file.fail(ctx.decl.src(), "TODO non-constant call inst?", .{}); + return ctx.fail(ctx.decl.src(), "TODO non-constant call inst?", .{}); } return null; } @@ -274,20 +310,20 @@ fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { return null; } -fn genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { - try ctx.file.main.writer().writeAll(indentation ++ "zig_unreachable();\n"); +fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { + try file.main.writer().writeAll(indentation ++ "zig_unreachable();\n"); return null; } -fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 { - const writer = ctx.file.main.writer(); +fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { + const writer = file.main.writer(); try writer.writeAll(indentation); for (as.inputs) |i, index| { if (i[0] == '{' and i[i.len - 1] == '}') { const reg = i[1 .. i.len - 1]; const arg = as.args[index]; try writer.writeAll("register "); - try renderType(ctx, writer, arg.ty); + try renderType(ctx, &file.header, writer, arg.ty); try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg }); // TODO merge constant handling into inst_map as well if (arg.castTag(.constant)) |c| { @@ -296,17 +332,17 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 { } else { const gop = try ctx.inst_map.getOrPut(arg); if (!gop.found_existing) { - return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); + return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); } try writer.print("{};\n ", .{gop.entry.value}); } } else { - return ctx.file.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); + return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); } } try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); if (as.output) |o| { - return ctx.file.fail(ctx.decl.src(), "TODO inline asm output", .{}); + return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{}); } if (as.inputs.len > 0) { if (as.output == null) { -- cgit v1.2.3 From 37438dd78975cf4ce46afaf4ba8c31d268d480bc Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 18 Nov 2020 20:50:51 -0500 Subject: CBE: add support for bool,u32 --- src/codegen/c.zig | 6 +++++- src/link/cbe.h | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 425663ad9c..589e2f17e0 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -26,15 +26,19 @@ fn renderType(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer try writer.writeAll("zig_noreturn void"); }, .Void => try writer.writeAll("void"), + .Bool => try writer.writeAll("bool"), .Int => { if (T.tag() == .u8) { header.need_stdint = true; try writer.writeAll("uint8_t"); + } else if (T.tag() == .u32) { + header.need_stdint = true; + try writer.writeAll("uint32_t"); } else if (T.tag() == .usize) { header.need_stddef = true; try writer.writeAll("size_t"); } else { - return ctx.fail(ctx.decl.src(), "TODO implement int types", .{}); + return ctx.fail(ctx.decl.src(), "TODO implement int type {}", .{T}); } }, else => |e| return ctx.fail(ctx.decl.src(), "TODO implement type {}", .{e}), diff --git a/src/link/cbe.h b/src/link/cbe.h index 854032227d..f35c6b8fff 100644 --- a/src/link/cbe.h +++ b/src/link/cbe.h @@ -1,3 +1,12 @@ +#if __STDC_VERSION__ >= 199901L +// C99 or newer +#include +#else +#define bool unsigned char +#define true 1 +#define false 0 +#endif + #if __STDC_VERSION__ >= 201112L #define zig_noreturn _Noreturn #elif __GNUC__ -- cgit v1.2.3 From 1634d45f1d53c8d7bfefa56ab4d2fa4cc8218b6d Mon Sep 17 00:00:00 2001 From: g-w1 <58830309+g-w1@users.noreply.github.com> Date: Fri, 25 Dec 2020 19:40:49 -0500 Subject: stage2: add compile log statement (#7191) --- src/Compilation.zig | 25 +++++++++++++------------ src/Module.zig | 46 +++++++++++++++++++++++++++++----------------- src/astgen.zig | 13 +++++++++++++ src/codegen/c.zig | 2 +- src/link/C.zig | 2 +- src/link/Coff.zig | 2 +- src/link/Elf.zig | 2 +- src/link/MachO.zig | 2 +- src/value.zig | 2 +- src/zir.zig | 23 ++++++++++++++++++++--- src/zir_sema.zig | 23 +++++++++++++++++++++++ test/stage2/test.zig | 15 +++++++++++++++ 12 files changed, 119 insertions(+), 38 deletions(-) (limited to 'src/codegen') diff --git a/src/Compilation.zig b/src/Compilation.zig index 885ecbb95c..6b5ae3abed 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1350,8 +1350,11 @@ pub fn totalErrorCount(self: *Compilation) usize { var total: usize = self.failed_c_objects.items().len; if (self.bin_file.options.module) |module| { - total += module.failed_decls.items().len + - module.failed_exports.items().len + + for (module.failed_decls.items()) |entry| { + assert(entry.value.items.len > 0); + total += entry.value.items.len; + } + total += module.failed_exports.items().len + module.failed_files.items().len + @boolToInt(module.failed_root_src_file != null); } @@ -1385,9 +1388,11 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { } for (module.failed_decls.items()) |entry| { const decl = entry.key; - const err_msg = entry.value; - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + const err_msg_list = entry.value; + for (err_msg_list.items) |err_msg| { + const source = try decl.scope.getSource(module); + try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + } } for (module.failed_exports.items()) |entry| { const decl = entry.key.owner_decl; @@ -1480,7 +1485,6 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor } assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); - self.bin_file.updateDecl(module, decl) catch |err| { switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -1488,8 +1492,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor decl.analysis = .dependency_failure; }, else => { - try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + try module.addDeclErr(decl, try ErrorMsg.create( module.gpa, decl.src(), "unable to codegen: {}", @@ -1508,8 +1511,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor decl.analysis = .dependency_failure; }, else => { - try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + try module.addDeclErr(decl, try ErrorMsg.create( module.gpa, decl.src(), "unable to generate C header: {}", @@ -1531,8 +1533,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .update_line_number => |decl| { const module = self.bin_file.options.module.?; self.bin_file.updateDeclLineNumber(module, decl) catch |err| { - try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + try module.addDeclErr(decl, try ErrorMsg.create( module.gpa, decl.src(), "unable to update line number: {}", diff --git a/src/Module.zig b/src/Module.zig index 20cb7bf195..897294069f 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -53,7 +53,7 @@ decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_has /// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator. /// Note that a Decl can succeed but the Fn it represents can fail. In this case, /// a Decl can have a failed_decls entry but have analysis status of success. -failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, +failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, ArrayListUnmanaged(*Compilation.ErrorMsg)) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, @@ -845,8 +845,11 @@ pub fn deinit(self: *Module) void { } self.decl_table.deinit(gpa); - for (self.failed_decls.items()) |entry| { - entry.value.destroy(gpa); + for (self.failed_decls.items()) |*entry| { + for (entry.value.items) |compile_err| { + compile_err.destroy(gpa); + } + entry.value.deinit(gpa); } self.failed_decls.deinit(gpa); @@ -942,8 +945,7 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => return error.AnalysisFail, else => { - try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); - self.failed_decls.putAssumeCapacityNoClobber(decl, try Compilation.ErrorMsg.create( + try self.addDeclErr(decl, try Compilation.ErrorMsg.create( self.gpa, decl.src(), "unable to analyze: {}", @@ -1550,7 +1552,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.analysis = .sema_failure; const err_msg = try Compilation.ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); errdefer err_msg.destroy(self.gpa); - try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); + try self.addDeclErr(decl, err_msg); } else { if (!srcHashEql(decl.contents_hash, contents_hash)) { try self.markOutdatedDecl(decl); @@ -1592,7 +1594,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.analysis = .sema_failure; const err_msg = try Compilation.ErrorMsg.create(self.gpa, name_loc.start, "redefinition of '{}'", .{decl.name}); errdefer err_msg.destroy(self.gpa); - try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); + try self.addDeclErr(decl, err_msg); } else if (!srcHashEql(decl.contents_hash, contents_hash)) { try self.markOutdatedDecl(decl); decl.contents_hash = contents_hash; @@ -1718,8 +1720,11 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { try self.markOutdatedDecl(dep); } } - if (self.failed_decls.remove(decl)) |entry| { - entry.value.destroy(self.gpa); + if (self.failed_decls.remove(decl)) |*entry| { + for (entry.value.items) |compile_err| { + compile_err.destroy(self.gpa); + } + entry.value.deinit(self.gpa); } self.deleteDeclExports(decl); self.comp.bin_file.freeDecl(decl); @@ -1798,8 +1803,11 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { fn markOutdatedDecl(self: *Module, decl: *Decl) !void { log.debug("mark {} outdated\n", .{decl.name}); try self.comp.work_queue.writeItem(.{ .analyze_decl = decl }); - if (self.failed_decls.remove(decl)) |entry| { - entry.value.destroy(self.gpa); + if (self.failed_decls.remove(decl)) |*entry| { + for (entry.value.items) |compile_err| { + compile_err.destroy(self.gpa); + } + entry.value.deinit(self.gpa); } decl.analysis = .outdated; } @@ -2944,10 +2952,14 @@ pub fn failNode( return self.fail(scope, src, format, args); } +pub fn addDeclErr(self: *Module, decl: *Decl, err: *Compilation.ErrorMsg) error{OutOfMemory}!void { + const entry = try self.failed_decls.getOrPutValue(self.gpa, decl, .{}); + try entry.value.append(self.gpa, err); +} + fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Compilation.ErrorMsg) InnerError { { errdefer err_msg.destroy(self.gpa); - try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); } switch (scope.tag) { @@ -2955,7 +2967,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com const decl = scope.cast(Scope.DeclAnalysis).?.decl; decl.analysis = .sema_failure; decl.generation = self.generation; - self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); + try self.addDeclErr(decl, err_msg); }, .block => { const block = scope.cast(Scope.Block).?; @@ -2965,25 +2977,25 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com block.decl.analysis = .sema_failure; block.decl.generation = self.generation; } - self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg); + try self.addDeclErr(block.decl, err_msg); }, .gen_zir => { const gen_zir = scope.cast(Scope.GenZIR).?; gen_zir.decl.analysis = .sema_failure; gen_zir.decl.generation = self.generation; - self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); + try self.addDeclErr(gen_zir.decl, err_msg); }, .local_val => { const gen_zir = scope.cast(Scope.LocalVal).?.gen_zir; gen_zir.decl.analysis = .sema_failure; gen_zir.decl.generation = self.generation; - self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); + try self.addDeclErr(gen_zir.decl, err_msg); }, .local_ptr => { const gen_zir = scope.cast(Scope.LocalPtr).?.gen_zir; gen_zir.decl.analysis = .sema_failure; gen_zir.decl.generation = self.generation; - self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); + try self.addDeclErr(gen_zir.decl, err_msg); }, .zir_module => { const zir_module = scope.cast(Scope.ZIRModule).?; diff --git a/src/astgen.zig b/src/astgen.zig index 622a18c443..ed5281ba42 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -2248,6 +2248,17 @@ fn import(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!* return addZIRUnOp(mod, scope, src, .import, target); } +fn compileLog(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + const tree = scope.tree(); + const arena = scope.arena(); + const src = tree.token_locs[call.builtin_token].start; + const params = call.params(); + var targets = try arena.alloc(*zir.Inst, params.len); + for (params) |param, param_i| + targets[param_i] = try expr(mod, scope, .none, param); + return addZIRInst(mod, scope, src, zir.Inst.CompileLog, .{ .to_log = targets }, .{}); +} + fn typeOf(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { const tree = scope.tree(); const arena = scope.arena(); @@ -2291,6 +2302,8 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built return rlWrap(mod, scope, rl, try addZIRNoOp(mod, scope, src, .breakpoint)); } else if (mem.eql(u8, builtin_name, "@import")) { return rlWrap(mod, scope, rl, try import(mod, scope, call)); + } else if (mem.eql(u8, builtin_name, "@compileLog")) { + return compileLog(mod, scope, call); } else { return mod.failTok(scope, call.builtin_token, "invalid builtin function: '{}'", .{builtin_name}); } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 589e2f17e0..73ac3e2f38 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -106,7 +106,7 @@ pub fn generateHeader( const writer = header.buf.writer(); renderFunctionSignature(&ctx, header, writer, decl) catch |err| { if (err == error.AnalysisFail) { - try module.failed_decls.put(module.gpa, decl, ctx.error_msg); + try module.addDeclErr(decl, ctx.error_msg); } return err; }; diff --git a/src/link/C.zig b/src/link/C.zig index 325f2ee048..cadbcf105f 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -106,7 +106,7 @@ pub fn deinit(self: *C) void { pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { codegen.generate(self, decl) catch |err| { if (err == error.AnalysisFail) { - try module.failed_decls.put(module.gpa, decl, self.error_msg); + try module.addDeclErr(decl, self.error_msg); } return err; }; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index bea3033f88..5ec4284623 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -658,7 +658,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { .appended => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; - try module.failed_decls.put(module.gpa, decl, em); + try module.addDeclErr(decl, em); return; }, }; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 6232a64e4a..2cd668c09f 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2248,7 +2248,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { .appended => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; - try module.failed_decls.put(module.gpa, decl, em); + try module.addDeclErr(decl, em); return; }, }; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 17816959a3..8c3001938f 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1062,7 +1062,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .appended => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; - try module.failed_decls.put(module.gpa, decl, em); + try module.addDeclErr(decl, em); return; }, }; diff --git a/src/value.zig b/src/value.zig index 4271ae66f4..56b3434400 100644 --- a/src/value.zig +++ b/src/value.zig @@ -350,7 +350,7 @@ pub const Value = extern union { val = elem_ptr.array_ptr; }, .empty_array => return out_stream.writeAll(".{}"), - .enum_literal => return out_stream.print(".{z}", .{self.cast(Payload.Bytes).?.data}), + .enum_literal => return out_stream.print(".{z}", .{@fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise).data}), .bytes => return out_stream.print("\"{Z}\"", .{self.cast(Payload.Bytes).?.data}), .repeated => { try out_stream.writeAll("(repeated) "); diff --git a/src/zir.zig b/src/zir.zig index f4f52cbc0f..982c98ddc9 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -122,6 +122,8 @@ pub const Inst = struct { coerce_to_ptr_elem, /// Emit an error message and fail compilation. compileerror, + /// Log compile time variables and emit an error message. + compilelog, /// Conditional branch. Splits control flow based on a boolean condition value. condbr, /// Special case, has no textual representation. @@ -386,6 +388,7 @@ pub const Inst = struct { .declval_in_module => DeclValInModule, .coerce_result_block_ptr => CoerceResultBlockPtr, .compileerror => CompileError, + .compilelog => CompileLog, .loop => Loop, .@"const" => Const, .str => Str, @@ -513,6 +516,7 @@ pub const Inst = struct { .slice_start, .import, .switch_range, + .compilelog, .typeof_peer, => false, @@ -707,6 +711,19 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const CompileLog = struct { + pub const base_tag = Tag.compilelog; + base: Inst, + + positionals: struct { + to_log: []*Inst, + }, + kw_args: struct { + /// If we have seen it already so don't make another error + seen: bool = false, + }, + }; + pub const Const = struct { pub const base_tag = Tag.@"const"; base: Inst, @@ -1925,7 +1942,7 @@ const EmitZIR = struct { .sema_failure, .sema_failure_retryable, .dependency_failure, - => if (self.old_module.failed_decls.get(ir_decl)) |err_msg| { + => if (self.old_module.failed_decls.get(ir_decl)) |err_msg_list| { const fail_inst = try self.arena.allocator.create(Inst.CompileError); fail_inst.* = .{ .base = .{ @@ -1933,7 +1950,7 @@ const EmitZIR = struct { .tag = Inst.CompileError.base_tag, }, .positionals = .{ - .msg = try self.arena.allocator.dupe(u8, err_msg.msg), + .msg = try self.arena.allocator.dupe(u8, err_msg_list.items[0].msg), }, .kw_args = .{}, }; @@ -2055,7 +2072,7 @@ const EmitZIR = struct { try self.emitBody(body, &inst_table, &instructions); }, .sema_failure => { - const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?; + const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?.items[0]; const fail_inst = try self.arena.allocator.create(Inst.CompileError); fail_inst.* = .{ .base = .{ diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 42d4577f71..16f2297ee6 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -43,6 +43,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .coerce_result_ptr => return analyzeInstCoerceResultPtr(mod, scope, old_inst.castTag(.coerce_result_ptr).?), .coerce_to_ptr_elem => return analyzeInstCoerceToPtrElem(mod, scope, old_inst.castTag(.coerce_to_ptr_elem).?), .compileerror => return analyzeInstCompileError(mod, scope, old_inst.castTag(.compileerror).?), + .compilelog => return analyzeInstCompileLog(mod, scope, old_inst.castTag(.compilelog).?), .@"const" => return analyzeInstConst(mod, scope, old_inst.castTag(.@"const").?), .dbg_stmt => return analyzeInstDbgStmt(mod, scope, old_inst.castTag(.dbg_stmt).?), .declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?), @@ -489,6 +490,28 @@ fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileE return mod.fail(scope, inst.base.src, "{}", .{inst.positionals.msg}); } +fn analyzeInstCompileLog(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileLog) InnerError!*Inst { + std.debug.print("| ", .{}); + for (inst.positionals.to_log) |item, i| { + const to_log = try resolveInst(mod, scope, item); + if (to_log.value()) |val| { + std.debug.print("{}", .{val}); + } else { + std.debug.print("(runtime value)", .{}); + } + if (i != inst.positionals.to_log.len - 1) std.debug.print(", ", .{}); + } + std.debug.print("\n", .{}); + if (!inst.kw_args.seen) { + inst.kw_args.seen = true; // so that we do not give multiple compile errors if it gets evaled twice + switch (mod.fail(scope, inst.base.src, "found compile log statement", .{})) { + error.AnalysisFail => {}, // analysis continues + else => |e| return e, + } + } + return mod.constVoid(scope, inst.base.src); +} + fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst { const b = try mod.requireRuntimeBlock(scope, inst.base.src); const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 3da497fb9b..d17ccc7b8b 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1171,6 +1171,21 @@ pub fn addCases(ctx: *TestContext) !void { \\fn entry() void {} , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); + ctx.compileError("compileLog", linux_x64, + \\export fn _start() noreturn { + \\ const b = true; + \\ var f: u32 = 1; + \\ @compileLog(b, 20, f, x, .foo); + \\ var y: u32 = true; + \\ unreachable; + \\} + \\fn x() void {} + , &[_][]const u8{ + ":4:3: error: found compile log statement", ":5:16: error: expected u32, found bool", + }); + + // "| true, 20, (runtime value), (function)" // TODO if this is here it invalidates the compile error checker. Need a way to check though. + { var case = ctx.obj("variable shadowing", linux_x64); case.addError( -- cgit v1.2.3 From f75d4cbe56f9f8212581f00700600a57ce545ba1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 28 Dec 2020 11:24:53 -0700 Subject: Revert "stage2: add compile log statement (#7191)" The addition of `addDeclErr` introduced a memory leak at every call site, and I also would like to push back on having more than 1 compilation error per `Decl`. This reverts commit 1634d45f1d53c8d7bfefa56ab4d2fa4cc8218b6d. --- src/Compilation.zig | 25 ++++++++++++------------- src/Module.zig | 46 +++++++++++++++++----------------------------- src/astgen.zig | 13 ------------- src/codegen/c.zig | 2 +- src/link/C.zig | 2 +- src/link/Coff.zig | 2 +- src/link/Elf.zig | 2 +- src/link/MachO.zig | 2 +- src/value.zig | 2 +- src/zir.zig | 25 ++++--------------------- src/zir_sema.zig | 23 ----------------------- test/stage2/test.zig | 16 +--------------- 12 files changed, 40 insertions(+), 120 deletions(-) (limited to 'src/codegen') diff --git a/src/Compilation.zig b/src/Compilation.zig index 6b5ae3abed..885ecbb95c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1350,11 +1350,8 @@ pub fn totalErrorCount(self: *Compilation) usize { var total: usize = self.failed_c_objects.items().len; if (self.bin_file.options.module) |module| { - for (module.failed_decls.items()) |entry| { - assert(entry.value.items.len > 0); - total += entry.value.items.len; - } - total += module.failed_exports.items().len + + total += module.failed_decls.items().len + + module.failed_exports.items().len + module.failed_files.items().len + @boolToInt(module.failed_root_src_file != null); } @@ -1388,11 +1385,9 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { } for (module.failed_decls.items()) |entry| { const decl = entry.key; - const err_msg_list = entry.value; - for (err_msg_list.items) |err_msg| { - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); - } + const err_msg = entry.value; + const source = try decl.scope.getSource(module); + try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); } for (module.failed_exports.items()) |entry| { const decl = entry.key.owner_decl; @@ -1485,6 +1480,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor } assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); + self.bin_file.updateDecl(module, decl) catch |err| { switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -1492,7 +1488,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor decl.analysis = .dependency_failure; }, else => { - try module.addDeclErr(decl, try ErrorMsg.create( + try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); + module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( module.gpa, decl.src(), "unable to codegen: {}", @@ -1511,7 +1508,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor decl.analysis = .dependency_failure; }, else => { - try module.addDeclErr(decl, try ErrorMsg.create( + try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); + module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( module.gpa, decl.src(), "unable to generate C header: {}", @@ -1533,7 +1531,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .update_line_number => |decl| { const module = self.bin_file.options.module.?; self.bin_file.updateDeclLineNumber(module, decl) catch |err| { - try module.addDeclErr(decl, try ErrorMsg.create( + try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); + module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( module.gpa, decl.src(), "unable to update line number: {}", diff --git a/src/Module.zig b/src/Module.zig index 5a4a76150c..c0b7011f43 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -53,7 +53,7 @@ decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_has /// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator. /// Note that a Decl can succeed but the Fn it represents can fail. In this case, /// a Decl can have a failed_decls entry but have analysis status of success. -failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, ArrayListUnmanaged(*Compilation.ErrorMsg)) = .{}, +failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, @@ -849,11 +849,8 @@ pub fn deinit(self: *Module) void { } self.decl_table.deinit(gpa); - for (self.failed_decls.items()) |*entry| { - for (entry.value.items) |compile_err| { - compile_err.destroy(gpa); - } - entry.value.deinit(gpa); + for (self.failed_decls.items()) |entry| { + entry.value.destroy(gpa); } self.failed_decls.deinit(gpa); @@ -949,7 +946,8 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => return error.AnalysisFail, else => { - try self.addDeclErr(decl, try Compilation.ErrorMsg.create( + try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); + self.failed_decls.putAssumeCapacityNoClobber(decl, try Compilation.ErrorMsg.create( self.gpa, decl.src(), "unable to analyze: {}", @@ -1556,7 +1554,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.analysis = .sema_failure; const err_msg = try Compilation.ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); errdefer err_msg.destroy(self.gpa); - try self.addDeclErr(decl, err_msg); + try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); } else { if (!srcHashEql(decl.contents_hash, contents_hash)) { try self.markOutdatedDecl(decl); @@ -1598,7 +1596,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.analysis = .sema_failure; const err_msg = try Compilation.ErrorMsg.create(self.gpa, name_loc.start, "redefinition of '{}'", .{decl.name}); errdefer err_msg.destroy(self.gpa); - try self.addDeclErr(decl, err_msg); + try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); } else if (!srcHashEql(decl.contents_hash, contents_hash)) { try self.markOutdatedDecl(decl); decl.contents_hash = contents_hash; @@ -1724,11 +1722,8 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { try self.markOutdatedDecl(dep); } } - if (self.failed_decls.remove(decl)) |*entry| { - for (entry.value.items) |compile_err| { - compile_err.destroy(self.gpa); - } - entry.value.deinit(self.gpa); + if (self.failed_decls.remove(decl)) |entry| { + entry.value.destroy(self.gpa); } self.deleteDeclExports(decl); self.comp.bin_file.freeDecl(decl); @@ -1807,11 +1802,8 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { fn markOutdatedDecl(self: *Module, decl: *Decl) !void { log.debug("mark {} outdated\n", .{decl.name}); try self.comp.work_queue.writeItem(.{ .analyze_decl = decl }); - if (self.failed_decls.remove(decl)) |*entry| { - for (entry.value.items) |compile_err| { - compile_err.destroy(self.gpa); - } - entry.value.deinit(self.gpa); + if (self.failed_decls.remove(decl)) |entry| { + entry.value.destroy(self.gpa); } decl.analysis = .outdated; } @@ -2956,14 +2948,10 @@ pub fn failNode( return self.fail(scope, src, format, args); } -pub fn addDeclErr(self: *Module, decl: *Decl, err: *Compilation.ErrorMsg) error{OutOfMemory}!void { - const entry = try self.failed_decls.getOrPutValue(self.gpa, decl, .{}); - try entry.value.append(self.gpa, err); -} - fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Compilation.ErrorMsg) InnerError { { errdefer err_msg.destroy(self.gpa); + try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); } switch (scope.tag) { @@ -2971,7 +2959,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com const decl = scope.cast(Scope.DeclAnalysis).?.decl; decl.analysis = .sema_failure; decl.generation = self.generation; - try self.addDeclErr(decl, err_msg); + self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); }, .block => { const block = scope.cast(Scope.Block).?; @@ -2981,25 +2969,25 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com block.decl.analysis = .sema_failure; block.decl.generation = self.generation; } - try self.addDeclErr(block.decl, err_msg); + self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg); }, .gen_zir => { const gen_zir = scope.cast(Scope.GenZIR).?; gen_zir.decl.analysis = .sema_failure; gen_zir.decl.generation = self.generation; - try self.addDeclErr(gen_zir.decl, err_msg); + self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); }, .local_val => { const gen_zir = scope.cast(Scope.LocalVal).?.gen_zir; gen_zir.decl.analysis = .sema_failure; gen_zir.decl.generation = self.generation; - try self.addDeclErr(gen_zir.decl, err_msg); + self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); }, .local_ptr => { const gen_zir = scope.cast(Scope.LocalPtr).?.gen_zir; gen_zir.decl.analysis = .sema_failure; gen_zir.decl.generation = self.generation; - try self.addDeclErr(gen_zir.decl, err_msg); + self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); }, .zir_module => { const zir_module = scope.cast(Scope.ZIRModule).?; diff --git a/src/astgen.zig b/src/astgen.zig index 100b2b9d02..672fe343e2 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -2333,17 +2333,6 @@ fn compileError(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerE return addZIRUnOp(mod, scope, src, .compileerror, target); } -fn compileLog(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { - const tree = scope.tree(); - const arena = scope.arena(); - const src = tree.token_locs[call.builtin_token].start; - const params = call.params(); - var targets = try arena.alloc(*zir.Inst, params.len); - for (params) |param, param_i| - targets[param_i] = try expr(mod, scope, .none, param); - return addZIRInst(mod, scope, src, zir.Inst.CompileLog, .{ .to_log = targets }, .{}); -} - fn typeOf(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { const tree = scope.tree(); const arena = scope.arena(); @@ -2389,8 +2378,6 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built return rlWrap(mod, scope, rl, try import(mod, scope, call)); } else if (mem.eql(u8, builtin_name, "@compileError")) { return compileError(mod, scope, call); - } else if (mem.eql(u8, builtin_name, "@compileLog")) { - return compileLog(mod, scope, call); } else { return mod.failTok(scope, call.builtin_token, "invalid builtin function: '{}'", .{builtin_name}); } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 73ac3e2f38..589e2f17e0 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -106,7 +106,7 @@ pub fn generateHeader( const writer = header.buf.writer(); renderFunctionSignature(&ctx, header, writer, decl) catch |err| { if (err == error.AnalysisFail) { - try module.addDeclErr(decl, ctx.error_msg); + try module.failed_decls.put(module.gpa, decl, ctx.error_msg); } return err; }; diff --git a/src/link/C.zig b/src/link/C.zig index cadbcf105f..325f2ee048 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -106,7 +106,7 @@ pub fn deinit(self: *C) void { pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { codegen.generate(self, decl) catch |err| { if (err == error.AnalysisFail) { - try module.addDeclErr(decl, self.error_msg); + try module.failed_decls.put(module.gpa, decl, self.error_msg); } return err; }; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 5ec4284623..bea3033f88 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -658,7 +658,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { .appended => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; - try module.addDeclErr(decl, em); + try module.failed_decls.put(module.gpa, decl, em); return; }, }; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 2cd668c09f..6232a64e4a 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2248,7 +2248,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { .appended => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; - try module.addDeclErr(decl, em); + try module.failed_decls.put(module.gpa, decl, em); return; }, }; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 8c3001938f..17816959a3 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1062,7 +1062,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { .appended => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; - try module.addDeclErr(decl, em); + try module.failed_decls.put(module.gpa, decl, em); return; }, }; diff --git a/src/value.zig b/src/value.zig index 56b3434400..4271ae66f4 100644 --- a/src/value.zig +++ b/src/value.zig @@ -350,7 +350,7 @@ pub const Value = extern union { val = elem_ptr.array_ptr; }, .empty_array => return out_stream.writeAll(".{}"), - .enum_literal => return out_stream.print(".{z}", .{@fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise).data}), + .enum_literal => return out_stream.print(".{z}", .{self.cast(Payload.Bytes).?.data}), .bytes => return out_stream.print("\"{Z}\"", .{self.cast(Payload.Bytes).?.data}), .repeated => { try out_stream.writeAll("(repeated) "); diff --git a/src/zir.zig b/src/zir.zig index 10f64be609..47e1abc24b 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -126,8 +126,6 @@ pub const Inst = struct { coerce_to_ptr_elem, /// Emit an error message and fail compilation. compileerror, - /// Log compile time variables and emit an error message. - compilelog, /// Conditional branch. Splits control flow based on a boolean condition value. condbr, /// Special case, has no textual representation. @@ -394,7 +392,6 @@ pub const Inst = struct { .declval => DeclVal, .declval_in_module => DeclValInModule, .coerce_result_block_ptr => CoerceResultBlockPtr, - .compilelog => CompileLog, .loop => Loop, .@"const" => Const, .str => Str, @@ -524,7 +521,6 @@ pub const Inst = struct { .slice_start, .import, .switch_range, - .compilelog, .typeof_peer, => false, @@ -709,19 +705,6 @@ pub const Inst = struct { kw_args: struct {}, }; - pub const CompileLog = struct { - pub const base_tag = Tag.compilelog; - base: Inst, - - positionals: struct { - to_log: []*Inst, - }, - kw_args: struct { - /// If we have seen it already so don't make another error - seen: bool = false, - }, - }; - pub const Const = struct { pub const base_tag = Tag.@"const"; base: Inst, @@ -1940,7 +1923,7 @@ const EmitZIR = struct { .sema_failure, .sema_failure_retryable, .dependency_failure, - => if (self.old_module.failed_decls.get(ir_decl)) |err_msg_list| { + => if (self.old_module.failed_decls.get(ir_decl)) |err_msg| { const fail_inst = try self.arena.allocator.create(Inst.UnOp); fail_inst.* = .{ .base = .{ @@ -1949,7 +1932,7 @@ const EmitZIR = struct { }, .positionals = .{ .operand = blk: { - const msg_str = try self.arena.allocator.dupe(u8, err_msg_list.items[0].msg); + const msg_str = try self.arena.allocator.dupe(u8, err_msg.msg); const str_inst = try self.arena.allocator.create(Inst.Str); str_inst.* = .{ @@ -1958,7 +1941,7 @@ const EmitZIR = struct { .tag = Inst.Str.base_tag, }, .positionals = .{ - .bytes = msg_str, + .bytes = err_msg.msg, }, .kw_args = .{}, }; @@ -2085,7 +2068,7 @@ const EmitZIR = struct { try self.emitBody(body, &inst_table, &instructions); }, .sema_failure => { - const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?.items[0]; + const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?; const fail_inst = try self.arena.allocator.create(Inst.UnOp); fail_inst.* = .{ .base = .{ diff --git a/src/zir_sema.zig b/src/zir_sema.zig index f3dae8f5b1..5700c18578 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -45,7 +45,6 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .coerce_result_ptr => return analyzeInstCoerceResultPtr(mod, scope, old_inst.castTag(.coerce_result_ptr).?), .coerce_to_ptr_elem => return analyzeInstCoerceToPtrElem(mod, scope, old_inst.castTag(.coerce_to_ptr_elem).?), .compileerror => return analyzeInstCompileError(mod, scope, old_inst.castTag(.compileerror).?), - .compilelog => return analyzeInstCompileLog(mod, scope, old_inst.castTag(.compilelog).?), .@"const" => return analyzeInstConst(mod, scope, old_inst.castTag(.@"const").?), .dbg_stmt => return analyzeInstDbgStmt(mod, scope, old_inst.castTag(.dbg_stmt).?), .declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?), @@ -503,28 +502,6 @@ fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) In return mod.fail(scope, inst.base.src, "{}", .{msg}); } -fn analyzeInstCompileLog(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileLog) InnerError!*Inst { - std.debug.print("| ", .{}); - for (inst.positionals.to_log) |item, i| { - const to_log = try resolveInst(mod, scope, item); - if (to_log.value()) |val| { - std.debug.print("{}", .{val}); - } else { - std.debug.print("(runtime value)", .{}); - } - if (i != inst.positionals.to_log.len - 1) std.debug.print(", ", .{}); - } - std.debug.print("\n", .{}); - if (!inst.kw_args.seen) { - inst.kw_args.seen = true; // so that we do not give multiple compile errors if it gets evaled twice - switch (mod.fail(scope, inst.base.src, "found compile log statement", .{})) { - error.AnalysisFail => {}, // analysis continues - else => |e| return e, - } - } - return mod.constVoid(scope, inst.base.src); -} - fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst { const b = try mod.requireRuntimeBlock(scope, inst.base.src); const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 4e57bbf9f7..5a200e9cf4 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -1171,27 +1171,13 @@ pub fn addCases(ctx: *TestContext) !void { \\fn entry() void {} , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); - ctx.compileError("compileLog", linux_x64, - \\export fn _start() noreturn { - \\ const b = true; - \\ var f: u32 = 1; - \\ @compileLog(b, 20, f, x, .foo); - \\ var y: u32 = true; - \\ unreachable; - \\} - \\fn x() void {} - , &[_][]const u8{ - ":4:3: error: found compile log statement", ":5:16: error: expected u32, found bool", - }); - - // "| true, 20, (runtime value), (function)" // TODO if this is here it invalidates the compile error checker. Need a way to check though. - ctx.compileError("compileError", linux_x64, \\export fn _start() noreturn { \\ @compileError("this is an error"); \\ unreachable; \\} , &[_][]const u8{":2:3: error: this is an error"}); + { var case = ctx.obj("variable shadowing", linux_x64); case.addError( -- cgit v1.2.3 From 071417161d5d7ab91c32073693590ef161017dbe Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Sat, 19 Dec 2020 11:22:49 +0100 Subject: stage2: add initial impl of LLVM backend in self-hosted compiler --- CMakeLists.txt | 2 +- src/Compilation.zig | 2 +- src/codegen/llvm.zig | 125 --------------- src/link/Elf.zig | 45 +++++- src/llvm_backend.zig | 410 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/llvm_bindings.zig | 358 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 813 insertions(+), 129 deletions(-) delete mode 100644 src/codegen/llvm.zig create mode 100644 src/llvm_backend.zig (limited to 'src/codegen') diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e002ca5c1..a9dc38b40b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -527,7 +527,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/codegen/aarch64.zig" "${CMAKE_SOURCE_DIR}/src/codegen/arm.zig" "${CMAKE_SOURCE_DIR}/src/codegen/c.zig" - "${CMAKE_SOURCE_DIR}/src/codegen/llvm.zig" "${CMAKE_SOURCE_DIR}/src/codegen/riscv64.zig" "${CMAKE_SOURCE_DIR}/src/codegen/spu-mk2.zig" "${CMAKE_SOURCE_DIR}/src/codegen/wasm.zig" @@ -549,6 +548,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/cbe.h" "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" "${CMAKE_SOURCE_DIR}/src/liveness.zig" + "${CMAKE_SOURCE_DIR}/src/llvm_backend.zig" "${CMAKE_SOURCE_DIR}/src/llvm_bindings.zig" "${CMAKE_SOURCE_DIR}/src/main.zig" "${CMAKE_SOURCE_DIR}/src/mingw.zig" diff --git a/src/Compilation.zig b/src/Compilation.zig index 885ecbb95c..26beeccd9e 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2106,7 +2106,7 @@ pub fn addCCArgs( try argv.append("-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS"); } - const llvm_triple = try @import("codegen/llvm.zig").targetTriple(arena, target); + const llvm_triple = try @import("llvm_backend.zig").targetTriple(arena, target); try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple }); switch (ext) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig deleted file mode 100644 index f87a7a940c..0000000000 --- a/src/codegen/llvm.zig +++ /dev/null @@ -1,125 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; - -pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 { - const llvm_arch = switch (target.cpu.arch) { - .arm => "arm", - .armeb => "armeb", - .aarch64 => "aarch64", - .aarch64_be => "aarch64_be", - .aarch64_32 => "aarch64_32", - .arc => "arc", - .avr => "avr", - .bpfel => "bpfel", - .bpfeb => "bpfeb", - .hexagon => "hexagon", - .mips => "mips", - .mipsel => "mipsel", - .mips64 => "mips64", - .mips64el => "mips64el", - .msp430 => "msp430", - .powerpc => "powerpc", - .powerpc64 => "powerpc64", - .powerpc64le => "powerpc64le", - .r600 => "r600", - .amdgcn => "amdgcn", - .riscv32 => "riscv32", - .riscv64 => "riscv64", - .sparc => "sparc", - .sparcv9 => "sparcv9", - .sparcel => "sparcel", - .s390x => "s390x", - .tce => "tce", - .tcele => "tcele", - .thumb => "thumb", - .thumbeb => "thumbeb", - .i386 => "i386", - .x86_64 => "x86_64", - .xcore => "xcore", - .nvptx => "nvptx", - .nvptx64 => "nvptx64", - .le32 => "le32", - .le64 => "le64", - .amdil => "amdil", - .amdil64 => "amdil64", - .hsail => "hsail", - .hsail64 => "hsail64", - .spir => "spir", - .spir64 => "spir64", - .kalimba => "kalimba", - .shave => "shave", - .lanai => "lanai", - .wasm32 => "wasm32", - .wasm64 => "wasm64", - .renderscript32 => "renderscript32", - .renderscript64 => "renderscript64", - .ve => "ve", - .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, - }; - // TODO Add a sub-arch for some architectures depending on CPU features. - - const llvm_os = switch (target.os.tag) { - .freestanding => "unknown", - .ananas => "ananas", - .cloudabi => "cloudabi", - .dragonfly => "dragonfly", - .freebsd => "freebsd", - .fuchsia => "fuchsia", - .ios => "ios", - .kfreebsd => "kfreebsd", - .linux => "linux", - .lv2 => "lv2", - .macos => "macosx", - .netbsd => "netbsd", - .openbsd => "openbsd", - .solaris => "solaris", - .windows => "windows", - .haiku => "haiku", - .minix => "minix", - .rtems => "rtems", - .nacl => "nacl", - .cnk => "cnk", - .aix => "aix", - .cuda => "cuda", - .nvcl => "nvcl", - .amdhsa => "amdhsa", - .ps4 => "ps4", - .elfiamcu => "elfiamcu", - .tvos => "tvos", - .watchos => "watchos", - .mesa3d => "mesa3d", - .contiki => "contiki", - .amdpal => "amdpal", - .hermit => "hermit", - .hurd => "hurd", - .wasi => "wasi", - .emscripten => "emscripten", - .uefi => "windows", - .other => "unknown", - }; - - const llvm_abi = switch (target.abi) { - .none => "unknown", - .gnu => "gnu", - .gnuabin32 => "gnuabin32", - .gnuabi64 => "gnuabi64", - .gnueabi => "gnueabi", - .gnueabihf => "gnueabihf", - .gnux32 => "gnux32", - .code16 => "code16", - .eabi => "eabi", - .eabihf => "eabihf", - .android => "android", - .musl => "musl", - .musleabi => "musleabi", - .musleabihf => "musleabihf", - .msvc => "msvc", - .itanium => "itanium", - .cygnus => "cygnus", - .coreclr => "coreclr", - .simulator => "simulator", - .macabi => "macabi", - }; - - return std.fmt.allocPrint(allocator, "{}-unknown-{}-{}", .{ llvm_arch, llvm_os, llvm_abi }); -} diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 6232a64e4a..479dc415e5 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -24,6 +24,7 @@ const build_options = @import("build_options"); const target_util = @import("../target.zig"); const glibc = @import("../glibc.zig"); const Cache = @import("../Cache.zig"); +const llvm_backend = @import("../llvm_backend.zig"); const default_entry_addr = 0x8000000; @@ -33,6 +34,9 @@ base: File, ptr_width: PtrWidth, +/// If this is not null, an object file is created by LLVM and linked with LLD afterwards. +llvm_ir_module: ?*llvm_backend.LLVMIRModule = null, + /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = std.ArrayListUnmanaged(elf.Elf64_Shdr){}, @@ -224,7 +228,13 @@ pub const SrcFn = struct { pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Elf { assert(options.object_format == .elf); - if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO + if (options.use_llvm) { + const self = try createEmpty(allocator, options); + errdefer self.base.destroy(); + + self.llvm_ir_module = try llvm_backend.LLVMIRModule.create(allocator, sub_path, options); + return self; + } const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = false, @@ -288,6 +298,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Elf { } pub fn deinit(self: *Elf) void { + if (self.llvm_ir_module) |ir_module| ir_module.deinit(self.base.allocator); self.sections.deinit(self.base.allocator); self.program_headers.deinit(self.base.allocator); self.shstrtab.deinit(self.base.allocator); @@ -423,6 +434,8 @@ fn updateString(self: *Elf, old_str_off: u32, new_name: []const u8) !u32 { } pub fn populateMissingMetadata(self: *Elf) !void { + if (self.llvm_ir_module) |_| return; + const small_ptr = switch (self.ptr_width) { .p32 => true, .p64 => false, @@ -727,6 +740,11 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); + if (self.llvm_ir_module) |llvm_ir_module| { + try llvm_ir_module.flushModule(comp); + return; + } + // TODO This linker code currently assumes there is only 1 compilation unit and it corresponds to the // Zig source code. const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; @@ -1261,6 +1279,9 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { const stack_size = self.base.options.stack_size_override orelse 16777216; const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os; const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt) blk: { + // TODO: remove when stage2 can build compiler_rt.zig + if (!build_options.is_stage1) break :blk null; + if (is_exe_or_dyn_lib) { break :blk comp.compiler_rt_static_lib.?.full_object_path; } else { @@ -1552,7 +1573,12 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { } // libc - if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies and !self.base.options.link_libc) { + // TODO: enable when stage2 can build c.zig + if (is_exe_or_dyn_lib and + !self.base.options.skip_linker_dependencies and + !self.base.options.link_libc and + build_options.is_stage1) + { try argv.append(comp.libc_static_lib.?.full_object_path); } @@ -2046,6 +2072,8 @@ fn allocateTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, al } pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { + if (self.llvm_ir_module) |_| return; + if (decl.link.elf.local_sym_index != 0) return; try self.local_symbols.ensureCapacity(self.base.allocator, self.local_symbols.items.len + 1); @@ -2082,6 +2110,8 @@ pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { } pub fn freeDecl(self: *Elf, decl: *Module.Decl) void { + if (self.llvm_ir_module) |_| return; + // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. self.freeTextBlock(&decl.link.elf); if (decl.link.elf.local_sym_index != 0) { @@ -2119,6 +2149,11 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { const tracy = trace(@src()); defer tracy.end(); + if (self.llvm_ir_module) |llvm_ir_module| { + try llvm_ir_module.updateDecl(module, decl); + return; + } + var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); @@ -2594,6 +2629,8 @@ pub fn updateDeclExports( decl: *const Module.Decl, exports: []const *Module.Export, ) !void { + if (self.llvm_ir_module) |_| return; + const tracy = trace(@src()); defer tracy.end(); @@ -2667,6 +2704,8 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec const tracy = trace(@src()); defer tracy.end(); + if (self.llvm_ir_module) |_| return; + const container_scope = decl.scope.cast(Module.Scope.Container).?; const tree = container_scope.file_scope.contents.tree; const file_ast_decls = tree.root_node.decls(); @@ -2685,6 +2724,8 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec } pub fn deleteExport(self: *Elf, exp: Export) void { + if (self.llvm_ir_module) |_| return; + const sym_index = exp.sym_index orelse return; self.global_symbol_free_list.append(self.base.allocator, sym_index) catch {}; self.global_symbols.items[sym_index].st_info = 0; diff --git a/src/llvm_backend.zig b/src/llvm_backend.zig new file mode 100644 index 0000000000..0745a1fe8b --- /dev/null +++ b/src/llvm_backend.zig @@ -0,0 +1,410 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Compilation = @import("Compilation.zig"); +const llvm = @import("llvm_bindings.zig"); +const link = @import("link.zig"); + +const Module = @import("Module.zig"); +const TypedValue = @import("TypedValue.zig"); +const ir = @import("ir.zig"); +const Inst = ir.Inst; + +const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; + +pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { + const llvm_arch = switch (target.cpu.arch) { + .arm => "arm", + .armeb => "armeb", + .aarch64 => "aarch64", + .aarch64_be => "aarch64_be", + .aarch64_32 => "aarch64_32", + .arc => "arc", + .avr => "avr", + .bpfel => "bpfel", + .bpfeb => "bpfeb", + .hexagon => "hexagon", + .mips => "mips", + .mipsel => "mipsel", + .mips64 => "mips64", + .mips64el => "mips64el", + .msp430 => "msp430", + .powerpc => "powerpc", + .powerpc64 => "powerpc64", + .powerpc64le => "powerpc64le", + .r600 => "r600", + .amdgcn => "amdgcn", + .riscv32 => "riscv32", + .riscv64 => "riscv64", + .sparc => "sparc", + .sparcv9 => "sparcv9", + .sparcel => "sparcel", + .s390x => "s390x", + .tce => "tce", + .tcele => "tcele", + .thumb => "thumb", + .thumbeb => "thumbeb", + .i386 => "i386", + .x86_64 => "x86_64", + .xcore => "xcore", + .nvptx => "nvptx", + .nvptx64 => "nvptx64", + .le32 => "le32", + .le64 => "le64", + .amdil => "amdil", + .amdil64 => "amdil64", + .hsail => "hsail", + .hsail64 => "hsail64", + .spir => "spir", + .spir64 => "spir64", + .kalimba => "kalimba", + .shave => "shave", + .lanai => "lanai", + .wasm32 => "wasm32", + .wasm64 => "wasm64", + .renderscript32 => "renderscript32", + .renderscript64 => "renderscript64", + .ve => "ve", + .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, + }; + // TODO Add a sub-arch for some architectures depending on CPU features. + + const llvm_os = switch (target.os.tag) { + .freestanding => "unknown", + .ananas => "ananas", + .cloudabi => "cloudabi", + .dragonfly => "dragonfly", + .freebsd => "freebsd", + .fuchsia => "fuchsia", + .ios => "ios", + .kfreebsd => "kfreebsd", + .linux => "linux", + .lv2 => "lv2", + .macos => "macosx", + .netbsd => "netbsd", + .openbsd => "openbsd", + .solaris => "solaris", + .windows => "windows", + .haiku => "haiku", + .minix => "minix", + .rtems => "rtems", + .nacl => "nacl", + .cnk => "cnk", + .aix => "aix", + .cuda => "cuda", + .nvcl => "nvcl", + .amdhsa => "amdhsa", + .ps4 => "ps4", + .elfiamcu => "elfiamcu", + .tvos => "tvos", + .watchos => "watchos", + .mesa3d => "mesa3d", + .contiki => "contiki", + .amdpal => "amdpal", + .hermit => "hermit", + .hurd => "hurd", + .wasi => "wasi", + .emscripten => "emscripten", + .uefi => "windows", + .other => "unknown", + }; + + const llvm_abi = switch (target.abi) { + .none => "unknown", + .gnu => "gnu", + .gnuabin32 => "gnuabin32", + .gnuabi64 => "gnuabi64", + .gnueabi => "gnueabi", + .gnueabihf => "gnueabihf", + .gnux32 => "gnux32", + .code16 => "code16", + .eabi => "eabi", + .eabihf => "eabihf", + .android => "android", + .musl => "musl", + .musleabi => "musleabi", + .musleabihf => "musleabihf", + .msvc => "msvc", + .itanium => "itanium", + .cygnus => "cygnus", + .coreclr => "coreclr", + .simulator => "simulator", + .macabi => "macabi", + }; + + return std.fmt.allocPrintZ(allocator, "{}-unknown-{}-{}", .{ llvm_arch, llvm_os, llvm_abi }); +} + +pub const LLVMIRModule = struct { + llvm_module: *const llvm.ModuleRef, + target_machine: *const llvm.TargetMachineRef, + output_path: []const u8, + + gpa: *Allocator, + err_msg: ?*Compilation.ErrorMsg = null, + + pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*LLVMIRModule { + const self = try allocator.create(LLVMIRModule); + errdefer allocator.destroy(self); + + const gpa = options.module.?.gpa; + + initializeLLVMTargets(); + + const root_nameZ = try gpa.dupeZ(u8, options.root_name); + defer gpa.free(root_nameZ); + const llvm_module = llvm.ModuleRef.createWithName(root_nameZ.ptr); + errdefer llvm_module.disposeModule(); + + const llvm_target_triple = try targetTriple(gpa, options.target); + defer gpa.free(llvm_target_triple); + + var error_message: [*:0]const u8 = undefined; + var target_ref: *const llvm.TargetRef = undefined; + if (llvm.TargetRef.getTargetFromTriple(llvm_target_triple.ptr, &target_ref, &error_message)) { + defer llvm.disposeMessage(error_message); + + const stderr = std.io.getStdErr().outStream(); + try stderr.print( + \\Zig is expecting LLVM to understand this target: '{s}' + \\However LLVM responded with: "{s}" + \\Zig is unable to continue. This is a bug in Zig: + \\https://github.com/ziglang/zig/issues/438 + \\ + , + .{ + llvm_target_triple, + error_message, + }, + ); + return error.InvalidLLVMTriple; + } + + const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug) .None else .Aggressive; + const target_machine = llvm.TargetMachineRef.createTargetMachine( + target_ref, + llvm_target_triple.ptr, + "", + "", + opt_level, + .Static, + .Default, + ); + errdefer target_machine.disposeTargetMachine(); + + self.* = .{ + .llvm_module = llvm_module, + .target_machine = target_machine, + .output_path = sub_path, + .gpa = gpa, + }; + return self; + } + + pub fn deinit(self: *LLVMIRModule, allocator: *Allocator) void { + self.llvm_module.disposeModule(); + self.target_machine.disposeTargetMachine(); + allocator.destroy(self); + } + + fn initializeLLVMTargets() void { + llvm.initializeAllTargets(); + llvm.initializeAllTargetInfos(); + llvm.initializeAllTargetMCs(); + llvm.initializeAllAsmPrinters(); + llvm.initializeAllAsmParsers(); + } + + pub fn flushModule(self: *LLVMIRModule, comp: *Compilation) !void { + { + var error_message: [*:0]const u8 = undefined; + // verifyModule always allocs the error_message even if there is no error + defer llvm.disposeMessage(error_message); + + if (self.llvm_module.verifyModule(.ReturnStatus, &error_message)) { + const stderr = std.io.getStdErr().outStream(); + try stderr.print("broken LLVM module found: {s}\nThis is a bug in the Zig compiler.", .{error_message}); + return error.BrokenLLVMModule; + } + } + + if (comp.verbose_llvm_ir) { + const dump = self.llvm_module.printToString(); + defer llvm.disposeMessage(dump); + + const stderr = std.io.getStdErr().outStream(); + try stderr.writeAll(std.mem.spanZ(dump)); + } + + const output_pathZ = try self.gpa.dupeZ(u8, self.output_path); + defer self.gpa.free(output_pathZ); + + var error_message: [*:0]const u8 = undefined; + // TODO: where to put the output object, zig-cache something? + // TODO: caching? + if (self.target_machine.emitToFile( + self.llvm_module, + output_pathZ.ptr, + .ObjectFile, + &error_message, + )) { + defer llvm.disposeMessage(error_message); + + const stderr = std.io.getStdErr().outStream(); + try stderr.print("LLVM failed to emit file: {s}\n", .{error_message}); + return error.FailedToEmit; + } + } + + pub fn updateDecl(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { + const typed_value = decl.typed_value.most_recent.typed_value; + self.generate(module, typed_value, decl.src()) catch |err| switch (err) { + error.CodegenFail => { + decl.analysis = .codegen_failure; + try module.failed_decls.put(module.gpa, decl, self.err_msg.?); + return; + }, + else => |e| return e, + }; + } + + fn generate(self: *LLVMIRModule, module: *Module, typed_value: TypedValue, src: usize) !void { + switch (typed_value.ty.zigTypeTag()) { + .Fn => { + const func = typed_value.val.cast(Value.Payload.Function).?.func; + + var codegen = CodeGen{ + .module = module, + .llvm_module = self.llvm_module, + .builder = llvm.BuilderRef.createBuilder(), + }; + defer codegen.builder.disposeBuilder(); + + const llvm_func = try codegen.resolveLLVMFunction(func); + + // We remove all the basic blocks of a function to support incremental + // compilation! + // TODO: remove all basic blocks if functions can have more than one + if (llvm_func.getFirstBasicBlock()) |bb| { + bb.deleteBasicBlock(); + } + + const entry_block = llvm_func.appendBasicBlock("Entry"); + codegen.builder.positionBuilderAtEnd(entry_block); + + const instructions = func.analysis.success.instructions; + for (instructions) |inst| { + switch (inst.tag) { + .breakpoint => try codegen.generateBreakpoint(inst.castTag(.breakpoint).?), + .call => try codegen.generateCall(inst.castTag(.call).?), + .unreach => codegen.generateUnreach(inst.castTag(.unreach).?), + .retvoid => codegen.generateRetVoid(inst.castTag(.retvoid).?), + .dbg_stmt => { + // TODO: implement debug info + }, + else => |tag| return self.fail(src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}), + } + } + }, + else => |ty| return self.fail(src, "TODO implement LLVM codegen for top-level decl type: {}", .{ty}), + } + } + + pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + @setCold(true); + std.debug.assert(self.err_msg == null); + self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args); + return error.CodegenFail; + } +}; + +const CodeGen = struct { + module: *Module, + llvm_module: *const llvm.ModuleRef, + builder: *const llvm.BuilderRef, + + fn generateCall(codegen: *CodeGen, inst: *Inst.Call) !void { + if (inst.func.cast(Inst.Constant)) |func_inst| { + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const func = func_val.func; + const zig_fn_type = func.owner_decl.typed_value.most_recent.typed_value.ty; + const llvm_fn = try codegen.resolveLLVMFunction(func); + + // TODO: handle more arguments, inst.args + + // TODO: LLVMBuildCall2 handles opaque function pointers, according to llvm docs + // Do we need that? + const call = codegen.builder.buildCall(llvm_fn, null, 0, ""); + + if (zig_fn_type.fnReturnType().zigTypeTag() == .NoReturn) { + _ = codegen.builder.buildUnreachable(); + } + } + } + } + + fn generateRetVoid(codegen: *CodeGen, inst: *Inst.NoOp) void { + _ = codegen.builder.buildRetVoid(); + } + + fn generateUnreach(codegen: *CodeGen, inst: *Inst.NoOp) void { + _ = codegen.builder.buildUnreachable(); + } + + fn generateBreakpoint(codegen: *CodeGen, inst: *Inst.NoOp) !void { + // TODO: Store this function somewhere such that we dont have to add it again + const fn_type = llvm.TypeRef.functionType(llvm.voidType(), null, 0, false); + const func = codegen.llvm_module.addFunction("llvm.debugtrap", fn_type); + // TODO: add assertion: LLVMGetIntrinsicID + _ = codegen.builder.buildCall(func, null, 0, ""); + } + + /// If the llvm function does not exist, create it + fn resolveLLVMFunction(codegen: *CodeGen, func: *Module.Fn) !*const llvm.ValueRef { + // TODO: do we want to store this in our own datastructure? + if (codegen.llvm_module.getNamedFunction(func.owner_decl.name)) |llvm_fn| return llvm_fn; + + const zig_fn_type = func.owner_decl.typed_value.most_recent.typed_value.ty; + const return_type = zig_fn_type.fnReturnType(); + + const fn_param_len = zig_fn_type.fnParamLen(); + + const fn_param_types = try codegen.module.gpa.alloc(Type, fn_param_len); + defer codegen.module.gpa.free(fn_param_types); + zig_fn_type.fnParamTypes(fn_param_types); + + const llvm_param = try codegen.module.gpa.alloc(*const llvm.TypeRef, fn_param_len); + defer codegen.module.gpa.free(llvm_param); + + for (fn_param_types) |fn_param, i| { + llvm_param[i] = codegen.getLLVMType(fn_param); + } + + const fn_type = llvm.TypeRef.functionType( + codegen.getLLVMType(return_type), + if (fn_param_len == 0) null else llvm_param.ptr, + @intCast(c_uint, fn_param_len), + false, + ); + const llvm_fn = codegen.llvm_module.addFunction(func.owner_decl.name, fn_type); + + if (return_type.zigTypeTag() == .NoReturn) { + llvm_fn.addFnAttr("noreturn"); + } + + return llvm_fn; + } + + fn getLLVMType(codegen: *CodeGen, t: Type) *const llvm.TypeRef { + switch (t.zigTypeTag()) { + .Void => return llvm.voidType(), + .NoReturn => return llvm.voidType(), + .Int => { + const info = t.intInfo(codegen.module.getTarget()); + return llvm.intType(info.bits); + }, + .Bool => return llvm.intType(1), + else => unreachable, + } + } +}; diff --git a/src/llvm_bindings.zig b/src/llvm_bindings.zig index 865e989c89..e36d71b312 100644 --- a/src/llvm_bindings.zig +++ b/src/llvm_bindings.zig @@ -1,6 +1,364 @@ //! We do this instead of @cImport because the self-hosted compiler is easier //! to bootstrap if it does not depend on translate-c. +const std = @import("std"); +const assert = std.debug.assert; + +const LLVMBool = bool; +pub const LLVMAttributeIndex = c_uint; + +pub const ValueRef = opaque { + pub const addAttributeAtIndex = LLVMAddAttributeAtIndex; + extern fn LLVMAddAttributeAtIndex(*const ValueRef, Idx: LLVMAttributeIndex, A: *const AttributeRef) void; + + pub const appendBasicBlock = LLVMAppendBasicBlock; + extern fn LLVMAppendBasicBlock(Fn: *const ValueRef, Name: [*:0]const u8) *const BasicBlockRef; + + pub const getFirstBasicBlock = LLVMGetFirstBasicBlock; + extern fn LLVMGetFirstBasicBlock(Fn: *const ValueRef) ?*const BasicBlockRef; + + // Helper functions + // TODO: Do we want to put these functions here? It allows for convienient function calls + // on ValueRef: llvm_fn.addFnAttr("noreturn") + fn addAttr(val: *const ValueRef, index: LLVMAttributeIndex, name: []const u8) void { + const kind_id = getEnumAttributeKindForName(name.ptr, name.len); + assert(kind_id != 0); + const llvm_attr = ContextRef.getGlobal().createEnumAttribute(kind_id, 0); + val.addAttributeAtIndex(index, llvm_attr); + } + + pub fn addFnAttr(val: *const ValueRef, attr_name: []const u8) void { + // TODO: improve this API, `addAttr(-1, attr_name)` + val.addAttr(std.math.maxInt(LLVMAttributeIndex), attr_name); + } +}; + +pub const TypeRef = opaque { + pub const functionType = LLVMFunctionType; + extern fn LLVMFunctionType(ReturnType: *const TypeRef, ParamTypes: ?[*]*const TypeRef, ParamCount: c_uint, IsVarArg: LLVMBool) *const TypeRef; +}; + +pub const ModuleRef = opaque { + pub const createWithName = LLVMModuleCreateWithName; + extern fn LLVMModuleCreateWithName(ModuleID: [*:0]const u8) *const ModuleRef; + + pub const disposeModule = LLVMDisposeModule; + extern fn LLVMDisposeModule(*const ModuleRef) void; + + pub const verifyModule = LLVMVerifyModule; + extern fn LLVMVerifyModule(*const ModuleRef, Action: VerifierFailureAction, OutMessage: *[*:0]const u8) LLVMBool; + + pub const addFunction = LLVMAddFunction; + extern fn LLVMAddFunction(*const ModuleRef, Name: [*:0]const u8, FunctionTy: *const TypeRef) *const ValueRef; + + pub const getNamedFunction = LLVMGetNamedFunction; + extern fn LLVMGetNamedFunction(*const ModuleRef, Name: [*:0]const u8) ?*const ValueRef; + + pub const printToString = LLVMPrintModuleToString; + extern fn LLVMPrintModuleToString(*const ModuleRef) [*:0]const u8; +}; + +pub const disposeMessage = LLVMDisposeMessage; +extern fn LLVMDisposeMessage(Message: [*:0]const u8) void; + +pub const VerifierFailureAction = extern enum { + AbortProcess, + PrintMessage, + ReturnStatus, +}; + +pub const voidType = LLVMVoidType; +extern fn LLVMVoidType() *const TypeRef; + +pub const getEnumAttributeKindForName = LLVMGetEnumAttributeKindForName; +extern fn LLVMGetEnumAttributeKindForName(Name: [*]const u8, SLen: usize) c_uint; + +pub const AttributeRef = opaque {}; + +pub const ContextRef = opaque { + pub const createEnumAttribute = LLVMCreateEnumAttribute; + extern fn LLVMCreateEnumAttribute(*const ContextRef, KindID: c_uint, Val: u64) *const AttributeRef; + + pub const getGlobal = LLVMGetGlobalContext; + extern fn LLVMGetGlobalContext() *const ContextRef; +}; + +pub const intType = LLVMIntType; +extern fn LLVMIntType(NumBits: c_uint) *const TypeRef; + +pub const BuilderRef = opaque { + pub const createBuilder = LLVMCreateBuilder; + extern fn LLVMCreateBuilder() *const BuilderRef; + + pub const disposeBuilder = LLVMDisposeBuilder; + extern fn LLVMDisposeBuilder(Builder: *const BuilderRef) void; + + pub const positionBuilderAtEnd = LLVMPositionBuilderAtEnd; + extern fn LLVMPositionBuilderAtEnd(Builder: *const BuilderRef, Block: *const BasicBlockRef) void; + + pub const getInsertBlock = LLVMGetInsertBlock; + extern fn LLVMGetInsertBlock(Builder: *const BuilderRef) *const BasicBlockRef; + + pub const buildCall = LLVMBuildCall; + extern fn LLVMBuildCall(*const BuilderRef, Fn: *const ValueRef, Args: ?[*]*const ValueRef, NumArgs: c_uint, Name: [*:0]const u8) *const ValueRef; + + pub const buildCall2 = LLVMBuildCall2; + extern fn LLVMBuildCall2(*const BuilderRef, *const TypeRef, Fn: *const ValueRef, Args: [*]*const ValueRef, NumArgs: c_uint, Name: [*:0]const u8) *const ValueRef; + + pub const buildRetVoid = LLVMBuildRetVoid; + extern fn LLVMBuildRetVoid(*const BuilderRef) *const ValueRef; + + pub const buildUnreachable = LLVMBuildUnreachable; + extern fn LLVMBuildUnreachable(*const BuilderRef) *const ValueRef; + + pub const buildAlloca = LLVMBuildAlloca; + extern fn LLVMBuildAlloca(*const BuilderRef, Ty: *const TypeRef, Name: [*:0]const u8) *const ValueRef; +}; + +pub const BasicBlockRef = opaque { + pub const deleteBasicBlock = LLVMDeleteBasicBlock; + extern fn LLVMDeleteBasicBlock(BB: *const BasicBlockRef) void; +}; + +pub const TargetMachineRef = opaque { + pub const createTargetMachine = LLVMCreateTargetMachine; + extern fn LLVMCreateTargetMachine( + T: *const TargetRef, + Triple: [*:0]const u8, + CPU: [*:0]const u8, + Features: [*:0]const u8, + Level: CodeGenOptLevel, + Reloc: RelocMode, + CodeModel: CodeMode, + ) *const TargetMachineRef; + + pub const disposeTargetMachine = LLVMDisposeTargetMachine; + extern fn LLVMDisposeTargetMachine(T: *const TargetMachineRef) void; + + pub const emitToFile = LLVMTargetMachineEmitToFile; + extern fn LLVMTargetMachineEmitToFile(*const TargetMachineRef, M: *const ModuleRef, Filename: [*:0]const u8, codegen: CodeGenFileType, ErrorMessage: *[*:0]const u8) LLVMBool; +}; + +pub const CodeMode = extern enum { + Default, + JITDefault, + Tiny, + Small, + Kernel, + Medium, + Large, +}; + +pub const CodeGenOptLevel = extern enum { + None, + Less, + Default, + Aggressive, +}; + +pub const RelocMode = extern enum { + Default, + Static, + PIC, + DynamicNoPic, + ROPI, + RWPI, + ROPI_RWPI, +}; + +pub const CodeGenFileType = extern enum { + AssemblyFile, + ObjectFile, +}; + +pub const TargetRef = opaque { + pub const getTargetFromTriple = LLVMGetTargetFromTriple; + extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **const TargetRef, ErrorMessage: *[*:0]const u8) LLVMBool; +}; + +extern fn LLVMInitializeAArch64TargetInfo() void; +extern fn LLVMInitializeAMDGPUTargetInfo() void; +extern fn LLVMInitializeARMTargetInfo() void; +extern fn LLVMInitializeAVRTargetInfo() void; +extern fn LLVMInitializeBPFTargetInfo() void; +extern fn LLVMInitializeHexagonTargetInfo() void; +extern fn LLVMInitializeLanaiTargetInfo() void; +extern fn LLVMInitializeMipsTargetInfo() void; +extern fn LLVMInitializeMSP430TargetInfo() void; +extern fn LLVMInitializeNVPTXTargetInfo() void; +extern fn LLVMInitializePowerPCTargetInfo() void; +extern fn LLVMInitializeRISCVTargetInfo() void; +extern fn LLVMInitializeSparcTargetInfo() void; +extern fn LLVMInitializeSystemZTargetInfo() void; +extern fn LLVMInitializeWebAssemblyTargetInfo() void; +extern fn LLVMInitializeX86TargetInfo() void; +extern fn LLVMInitializeXCoreTargetInfo() void; +extern fn LLVMInitializeAArch64Target() void; +extern fn LLVMInitializeAMDGPUTarget() void; +extern fn LLVMInitializeARMTarget() void; +extern fn LLVMInitializeAVRTarget() void; +extern fn LLVMInitializeBPFTarget() void; +extern fn LLVMInitializeHexagonTarget() void; +extern fn LLVMInitializeLanaiTarget() void; +extern fn LLVMInitializeMipsTarget() void; +extern fn LLVMInitializeMSP430Target() void; +extern fn LLVMInitializeNVPTXTarget() void; +extern fn LLVMInitializePowerPCTarget() void; +extern fn LLVMInitializeRISCVTarget() void; +extern fn LLVMInitializeSparcTarget() void; +extern fn LLVMInitializeSystemZTarget() void; +extern fn LLVMInitializeWebAssemblyTarget() void; +extern fn LLVMInitializeX86Target() void; +extern fn LLVMInitializeXCoreTarget() void; +extern fn LLVMInitializeAArch64TargetMC() void; +extern fn LLVMInitializeAMDGPUTargetMC() void; +extern fn LLVMInitializeARMTargetMC() void; +extern fn LLVMInitializeAVRTargetMC() void; +extern fn LLVMInitializeBPFTargetMC() void; +extern fn LLVMInitializeHexagonTargetMC() void; +extern fn LLVMInitializeLanaiTargetMC() void; +extern fn LLVMInitializeMipsTargetMC() void; +extern fn LLVMInitializeMSP430TargetMC() void; +extern fn LLVMInitializeNVPTXTargetMC() void; +extern fn LLVMInitializePowerPCTargetMC() void; +extern fn LLVMInitializeRISCVTargetMC() void; +extern fn LLVMInitializeSparcTargetMC() void; +extern fn LLVMInitializeSystemZTargetMC() void; +extern fn LLVMInitializeWebAssemblyTargetMC() void; +extern fn LLVMInitializeX86TargetMC() void; +extern fn LLVMInitializeXCoreTargetMC() void; +extern fn LLVMInitializeAArch64AsmPrinter() void; +extern fn LLVMInitializeAMDGPUAsmPrinter() void; +extern fn LLVMInitializeARMAsmPrinter() void; +extern fn LLVMInitializeAVRAsmPrinter() void; +extern fn LLVMInitializeBPFAsmPrinter() void; +extern fn LLVMInitializeHexagonAsmPrinter() void; +extern fn LLVMInitializeLanaiAsmPrinter() void; +extern fn LLVMInitializeMipsAsmPrinter() void; +extern fn LLVMInitializeMSP430AsmPrinter() void; +extern fn LLVMInitializeNVPTXAsmPrinter() void; +extern fn LLVMInitializePowerPCAsmPrinter() void; +extern fn LLVMInitializeRISCVAsmPrinter() void; +extern fn LLVMInitializeSparcAsmPrinter() void; +extern fn LLVMInitializeSystemZAsmPrinter() void; +extern fn LLVMInitializeWebAssemblyAsmPrinter() void; +extern fn LLVMInitializeX86AsmPrinter() void; +extern fn LLVMInitializeXCoreAsmPrinter() void; +extern fn LLVMInitializeAArch64AsmParser() void; +extern fn LLVMInitializeAMDGPUAsmParser() void; +extern fn LLVMInitializeARMAsmParser() void; +extern fn LLVMInitializeAVRAsmParser() void; +extern fn LLVMInitializeBPFAsmParser() void; +extern fn LLVMInitializeHexagonAsmParser() void; +extern fn LLVMInitializeLanaiAsmParser() void; +extern fn LLVMInitializeMipsAsmParser() void; +extern fn LLVMInitializeMSP430AsmParser() void; +extern fn LLVMInitializePowerPCAsmParser() void; +extern fn LLVMInitializeRISCVAsmParser() void; +extern fn LLVMInitializeSparcAsmParser() void; +extern fn LLVMInitializeSystemZAsmParser() void; +extern fn LLVMInitializeWebAssemblyAsmParser() void; +extern fn LLVMInitializeX86AsmParser() void; + +pub const initializeAllTargetInfos = LLVMInitializeAllTargetInfos; +fn LLVMInitializeAllTargetInfos() callconv(.C) void { + LLVMInitializeAArch64TargetInfo(); + LLVMInitializeAMDGPUTargetInfo(); + LLVMInitializeARMTargetInfo(); + LLVMInitializeAVRTargetInfo(); + LLVMInitializeBPFTargetInfo(); + LLVMInitializeHexagonTargetInfo(); + LLVMInitializeLanaiTargetInfo(); + LLVMInitializeMipsTargetInfo(); + LLVMInitializeMSP430TargetInfo(); + LLVMInitializeNVPTXTargetInfo(); + LLVMInitializePowerPCTargetInfo(); + LLVMInitializeRISCVTargetInfo(); + LLVMInitializeSparcTargetInfo(); + LLVMInitializeSystemZTargetInfo(); + LLVMInitializeWebAssemblyTargetInfo(); + LLVMInitializeX86TargetInfo(); + LLVMInitializeXCoreTargetInfo(); +} +pub const initializeAllTargets = LLVMInitializeAllTargets; +fn LLVMInitializeAllTargets() callconv(.C) void { + LLVMInitializeAArch64Target(); + LLVMInitializeAMDGPUTarget(); + LLVMInitializeARMTarget(); + LLVMInitializeAVRTarget(); + LLVMInitializeBPFTarget(); + LLVMInitializeHexagonTarget(); + LLVMInitializeLanaiTarget(); + LLVMInitializeMipsTarget(); + LLVMInitializeMSP430Target(); + LLVMInitializeNVPTXTarget(); + LLVMInitializePowerPCTarget(); + LLVMInitializeRISCVTarget(); + LLVMInitializeSparcTarget(); + LLVMInitializeSystemZTarget(); + LLVMInitializeWebAssemblyTarget(); + LLVMInitializeX86Target(); + LLVMInitializeXCoreTarget(); +} +pub const initializeAllTargetMCs = LLVMInitializeAllTargetMCs; +fn LLVMInitializeAllTargetMCs() callconv(.C) void { + LLVMInitializeAArch64TargetMC(); + LLVMInitializeAMDGPUTargetMC(); + LLVMInitializeARMTargetMC(); + LLVMInitializeAVRTargetMC(); + LLVMInitializeBPFTargetMC(); + LLVMInitializeHexagonTargetMC(); + LLVMInitializeLanaiTargetMC(); + LLVMInitializeMipsTargetMC(); + LLVMInitializeMSP430TargetMC(); + LLVMInitializeNVPTXTargetMC(); + LLVMInitializePowerPCTargetMC(); + LLVMInitializeRISCVTargetMC(); + LLVMInitializeSparcTargetMC(); + LLVMInitializeSystemZTargetMC(); + LLVMInitializeWebAssemblyTargetMC(); + LLVMInitializeX86TargetMC(); + LLVMInitializeXCoreTargetMC(); +} +pub const initializeAllAsmPrinters = LLVMInitializeAllAsmPrinters; +fn LLVMInitializeAllAsmPrinters() callconv(.C) void { + LLVMInitializeAArch64AsmPrinter(); + LLVMInitializeAMDGPUAsmPrinter(); + LLVMInitializeARMAsmPrinter(); + LLVMInitializeAVRAsmPrinter(); + LLVMInitializeBPFAsmPrinter(); + LLVMInitializeHexagonAsmPrinter(); + LLVMInitializeLanaiAsmPrinter(); + LLVMInitializeMipsAsmPrinter(); + LLVMInitializeMSP430AsmPrinter(); + LLVMInitializeNVPTXAsmPrinter(); + LLVMInitializePowerPCAsmPrinter(); + LLVMInitializeRISCVAsmPrinter(); + LLVMInitializeSparcAsmPrinter(); + LLVMInitializeSystemZAsmPrinter(); + LLVMInitializeWebAssemblyAsmPrinter(); + LLVMInitializeX86AsmPrinter(); + LLVMInitializeXCoreAsmPrinter(); +} +pub const initializeAllAsmParsers = LLVMInitializeAllAsmParsers; +fn LLVMInitializeAllAsmParsers() callconv(.C) void { + LLVMInitializeAArch64AsmParser(); + LLVMInitializeAMDGPUAsmParser(); + LLVMInitializeARMAsmParser(); + LLVMInitializeAVRAsmParser(); + LLVMInitializeBPFAsmParser(); + LLVMInitializeHexagonAsmParser(); + LLVMInitializeLanaiAsmParser(); + LLVMInitializeMipsAsmParser(); + LLVMInitializeMSP430AsmParser(); + LLVMInitializePowerPCAsmParser(); + LLVMInitializeRISCVAsmParser(); + LLVMInitializeSparcAsmParser(); + LLVMInitializeSystemZAsmParser(); + LLVMInitializeWebAssemblyAsmParser(); + LLVMInitializeX86AsmParser(); +} + extern fn ZigLLDLinkCOFF(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; extern fn ZigLLDLinkELF(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; extern fn ZigLLDLinkMachO(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; -- cgit v1.2.3 From 87c6341b61aa54301aa98fea1a449fff40ba25af Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 28 Dec 2020 17:15:29 -0700 Subject: stage2: add extern functions and improve the C backend enough to support Hello World (almost) --- src/Compilation.zig | 5 +- src/Module.zig | 97 ++++++++++++-- src/codegen/c.zig | 377 ++++++++++++++++++++++++++++++++++------------------ src/link/C.zig | 16 +-- src/link/cbe.h | 4 + src/type.zig | 92 ++++++++++++- src/value.zig | 24 ++++ 7 files changed, 452 insertions(+), 163 deletions(-) (limited to 'src/codegen') diff --git a/src/Compilation.zig b/src/Compilation.zig index 26beeccd9e..39dd97c3a2 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1431,9 +1431,6 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len); defer c_comp_progress_node.end(); - var arena = std.heap.ArenaAllocator.init(self.gpa); - defer arena.deinit(); - self.work_queue_wait_group.reset(); defer self.work_queue_wait_group.wait(); @@ -1502,7 +1499,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor }; if (self.c_header) |*header| { - c_codegen.generateHeader(&arena, module, &header.*, decl) catch |err| switch (err) { + c_codegen.generateHeader(self, module, header, decl) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => { decl.analysis = .dependency_failure; diff --git a/src/Module.zig b/src/Module.zig index c0b7011f43..dbb845116a 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -277,6 +277,8 @@ pub const Decl = struct { }; /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. +/// Extern functions do not have this data structure; they are represented by +/// the `Decl` only, with a `Value` tag of `extern_fn`. pub const Fn = struct { /// This memory owned by the Decl's TypedValue.Managed arena allocator. analysis: union(enum) { @@ -1010,8 +1012,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { defer fn_type_scope.instructions.deinit(self.gpa); decl.is_pub = fn_proto.getVisibToken() != null; - const body_node = fn_proto.getBodyNode() orelse - return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); const param_decls = fn_proto.params(); const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_decls.len); @@ -1083,6 +1083,36 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, fn_type_inst, .{ .instructions = fn_type_scope.instructions.items, }); + const body_node = fn_proto.getBodyNode() orelse { + // Extern function. + var type_changed = true; + if (decl.typedValueManaged()) |tvm| { + type_changed = !tvm.typed_value.ty.eql(fn_type); + + tvm.deinit(self.gpa); + } + const value_payload = try decl_arena.allocator.create(Value.Payload.ExternFn); + value_payload.* = .{ .decl = decl }; + + decl_arena_state.* = decl_arena.state; + decl.typed_value = .{ + .most_recent = .{ + .typed_value = .{ + .ty = fn_type, + .val = Value.initPayload(&value_payload.base), + }, + .arena = decl_arena_state, + }, + }; + decl.analysis = .complete; + decl.generation = self.generation; + + try self.comp.bin_file.allocateDeclIndexes(decl); + try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); + + return type_changed; + }; + const new_func = try decl_arena.allocator.create(Fn); const fn_payload = try decl_arena.allocator.create(Value.Payload.Function); @@ -1899,7 +1929,13 @@ pub fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { return null; } -pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, borrowed_symbol_name: []const u8, exported_decl: *Decl) !void { +pub fn analyzeExport( + self: *Module, + scope: *Scope, + src: usize, + borrowed_symbol_name: []const u8, + exported_decl: *Decl, +) !void { try self.ensureDeclAnalyzed(exported_decl); const typed_value = exported_decl.typed_value.most_recent.typed_value; switch (typed_value.ty.zigTypeTag()) { @@ -2801,16 +2837,47 @@ pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst } } - // *[N]T to []T - if (inst.ty.isSinglePointer() and dest_type.isSlice() and - (!inst.ty.isConstPtr() or dest_type.isConstPtr())) - { + // Coercions where the source is a single pointer to an array. + src_array_ptr: { + if (!inst.ty.isSinglePointer()) break :src_array_ptr; const array_type = inst.ty.elemType(); + if (array_type.zigTypeTag() != .Array) break :src_array_ptr; + const array_elem_type = array_type.elemType(); + if (inst.ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr; + if (inst.ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr; + const dst_elem_type = dest_type.elemType(); - if (array_type.zigTypeTag() == .Array and - coerceInMemoryAllowed(dst_elem_type, array_type.elemType()) == .ok) - { - return self.coerceArrayPtrToSlice(scope, dest_type, inst); + switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) { + .ok => {}, + .no_match => break :src_array_ptr, + } + + switch (dest_type.ptrSize()) { + .Slice => { + // *[N]T to []T + return self.coerceArrayPtrToSlice(scope, dest_type, inst); + }, + .C => { + // *[N]T to [*c]T + return self.coerceArrayPtrToMany(scope, dest_type, inst); + }, + .Many => { + // *[N]T to [*]T + // *[N:s]T to [*:s]T + const src_sentinel = array_type.sentinel(); + const dst_sentinel = dest_type.sentinel(); + if (src_sentinel == null and dst_sentinel == null) + return self.coerceArrayPtrToMany(scope, dest_type, inst); + + if (src_sentinel) |src_s| { + if (dst_sentinel) |dst_s| { + if (src_s.eql(dst_s)) { + return self.coerceArrayPtrToMany(scope, dest_type, inst); + } + } + } + }, + .One => {}, } } @@ -2918,6 +2985,14 @@ fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *I return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{}); } +fn coerceArrayPtrToMany(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { + if (inst.value()) |val| { + // The comptime Value representation is compatible with both types. + return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); + } + return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToMany runtime instruction", .{}); +} + pub fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: anytype) InnerError { @setCold(true); const err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 589e2f17e0..8d706f8735 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -12,7 +12,7 @@ const C = link.File.C; const Decl = Module.Decl; const mem = std.mem; -const indentation = " "; +const Writer = std.ArrayList(u8).Writer; /// Maps a name from Zig source to C. Currently, this will always give the same /// output for any given input, sometimes resulting in broken identifiers. @@ -20,43 +20,145 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { return allocator.dupe(u8, name); } -fn renderType(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, T: Type) !void { - switch (T.zigTypeTag()) { +fn renderType( + ctx: *Context, + header: *C.Header, + writer: Writer, + t: Type, +) error{ OutOfMemory, AnalysisFail }!void { + switch (t.zigTypeTag()) { .NoReturn => { try writer.writeAll("zig_noreturn void"); }, .Void => try writer.writeAll("void"), .Bool => try writer.writeAll("bool"), .Int => { - if (T.tag() == .u8) { - header.need_stdint = true; - try writer.writeAll("uint8_t"); - } else if (T.tag() == .u32) { - header.need_stdint = true; - try writer.writeAll("uint32_t"); - } else if (T.tag() == .usize) { - header.need_stddef = true; - try writer.writeAll("size_t"); + switch (t.tag()) { + .u8 => try writer.writeAll("uint8_t"), + .i8 => try writer.writeAll("int8_t"), + .u16 => try writer.writeAll("uint16_t"), + .i16 => try writer.writeAll("int16_t"), + .u32 => try writer.writeAll("uint32_t"), + .i32 => try writer.writeAll("int32_t"), + .u64 => try writer.writeAll("uint64_t"), + .i64 => try writer.writeAll("int64_t"), + .usize => try writer.writeAll("uintptr_t"), + .isize => try writer.writeAll("intptr_t"), + .c_short => try writer.writeAll("short"), + .c_ushort => try writer.writeAll("unsigned short"), + .c_int => try writer.writeAll("int"), + .c_uint => try writer.writeAll("unsigned int"), + .c_long => try writer.writeAll("long"), + .c_ulong => try writer.writeAll("unsigned long"), + .c_longlong => try writer.writeAll("long long"), + .c_ulonglong => try writer.writeAll("unsigned long long"), + .int_signed, .int_unsigned => { + const info = t.intInfo(ctx.target); + const sign_prefix = switch (info.signedness) { + .signed => "i", + .unsigned => "", + }; + inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { + if (info.bits <= nbits) { + try writer.print("{s}int{d}_t", .{ sign_prefix, nbits }); + break; + } + } else { + return ctx.fail(ctx.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); + } + }, + else => unreachable, + } + }, + .Pointer => { + if (t.isSlice()) { + return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{}); } else { - return ctx.fail(ctx.decl.src(), "TODO implement int type {}", .{T}); + if (t.isConstPtr()) { + try writer.writeAll("const "); + } + if (t.isVolatilePtr()) { + try writer.writeAll("volatile "); + } + try renderType(ctx, header, writer, t.elemType()); + try writer.writeAll(" *"); } }, - else => |e| return ctx.fail(ctx.decl.src(), "TODO implement type {}", .{e}), + .Array => { + try renderType(ctx, header, writer, t.elemType()); + const sentinel_bit = @boolToInt(t.sentinel() != null); + const c_len = t.arrayLen() + sentinel_bit; + try writer.print("[{d}]", .{c_len}); + }, + else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement type {s}", .{ + @tagName(e), + }), } } -fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Value) !void { - switch (T.zigTypeTag()) { +fn renderValue( + ctx: *Context, + writer: Writer, + t: Type, + val: Value, +) error{ OutOfMemory, AnalysisFail }!void { + switch (t.zigTypeTag()) { .Int => { - if (T.isSignedInt()) - return writer.print("{}", .{val.toSignedInt()}); - return writer.print("{}", .{val.toUnsignedInt()}); + if (t.isSignedInt()) + return writer.print("{d}", .{val.toSignedInt()}); + return writer.print("{d}", .{val.toUnsignedInt()}); + }, + .Pointer => switch (val.tag()) { + .undef, .zero => try writer.writeAll("0"), + .one => try writer.writeAll("1"), + .decl_ref => { + const decl_ref_payload = val.cast(Value.Payload.DeclRef).?; + try writer.print("&{s}", .{decl_ref_payload.decl.name}); + }, + .function => { + const payload = val.cast(Value.Payload.Function).?; + try writer.print("{s}", .{payload.func.owner_decl.name}); + }, + .extern_fn => { + const payload = val.cast(Value.Payload.ExternFn).?; + try writer.print("{s}", .{payload.decl.name}); + }, + else => |e| return ctx.fail( + ctx.decl.src(), + "TODO: C backend: implement Pointer value {s}", + .{@tagName(e)}, + ), + }, + .Array => { + // TODO first try specific tag representations for more efficiency + // Fall back to inefficient generic implementation. + try writer.writeAll("{"); + var index: usize = 0; + const len = t.arrayLen(); + const elem_ty = t.elemType(); + while (index < len) : (index += 1) { + if (index != 0) try writer.writeAll(","); + const elem_val = try val.elemValue(&ctx.arena.allocator, index); + try renderValue(ctx, writer, elem_ty, elem_val); + } + if (t.sentinel()) |sentinel_val| { + if (index != 0) try writer.writeAll(","); + try renderValue(ctx, writer, elem_ty, sentinel_val); + } + try writer.writeAll("}"); }, - else => |e| return ctx.fail(ctx.decl.src(), "TODO implement value {}", .{e}), + else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{ + @tagName(e), + }), } } -fn renderFunctionSignature(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { +fn renderFunctionSignature( + ctx: *Context, + header: *C.Header, + writer: Writer, + decl: *Decl, +) !void { const tv = decl.typed_value.most_recent.typed_value; try renderType(ctx, header, writer, tv.ty.fnReturnType()); // Use the child allocator directly, as we know the name can be freed before @@ -81,27 +183,92 @@ fn renderFunctionSignature(ctx: *Context, header: *C.Header, writer: std.ArrayLi } pub fn generate(file: *C, decl: *Decl) !void { - switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { - .Fn => try genFn(file, decl), - .Array => try genArray(file, decl), - else => |e| return file.fail(decl.src(), "TODO {}", .{e}), + const tv = decl.typed_value.most_recent.typed_value; + + var arena = std.heap.ArenaAllocator.init(file.base.allocator); + defer arena.deinit(); + var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); + defer inst_map.deinit(); + var ctx = Context{ + .decl = decl, + .arena = &arena, + .inst_map = &inst_map, + .target = file.base.options.target, + }; + defer { + file.error_msg = ctx.error_msg; + ctx.deinit(); + } + + if (tv.val.cast(Value.Payload.Function)) |func_payload| { + const writer = file.main.writer(); + try renderFunctionSignature(&ctx, &file.header, writer, decl); + + try writer.writeAll(" {"); + + const func: *Module.Fn = func_payload.func; + const instructions = func.analysis.success.instructions; + if (instructions.len > 0) { + try writer.writeAll("\n"); + for (instructions) |inst| { + const indent_size = 4; + const indent_level = 1; + try writer.writeByteNTimes(' ', indent_size * indent_level); + if (switch (inst.tag) { + .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), + .call => try genCall(&ctx, file, inst.castTag(.call).?), + .add => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "+"), + .sub => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "-"), + .ret => try genRet(&ctx, file, inst.castTag(.ret).?), + .retvoid => try genRetVoid(file), + .arg => try genArg(&ctx), + .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), + .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?), + .unreach => try genUnreach(file, inst.castTag(.unreach).?), + .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), + else => |e| return ctx.fail(decl.src(), "TODO implement C codegen for {}", .{e}), + }) |name| { + try ctx.inst_map.putNoClobber(inst, name); + } + } + } + + try writer.writeAll("}\n\n"); + } else if (tv.val.tag() == .extern_fn) { + return; // handled when referenced + } else { + const writer = file.constants.writer(); + try writer.writeAll("static "); + + // TODO ask the Decl if it is const + // https://github.com/ziglang/zig/issues/7582 + + try renderType(&ctx, &file.header, writer, tv.ty); + try writer.print(" {s} = ", .{decl.name}); + try renderValue(&ctx, writer, tv.ty, tv.val); + try writer.writeAll(";\n"); } } pub fn generateHeader( - arena: *std.heap.ArenaAllocator, + comp: *Compilation, module: *Module, header: *C.Header, decl: *Decl, ) error{ AnalysisFail, OutOfMemory }!void { switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { .Fn => { - var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); + var inst_map = std.AutoHashMap(*Inst, []u8).init(comp.gpa); defer inst_map.deinit(); + + var arena = std.heap.ArenaAllocator.init(comp.gpa); + defer arena.deinit(); + var ctx = Context{ .decl = decl, - .arena = arena, + .arena = &arena, .inst_map = &inst_map, + .target = comp.getTarget(), }; const writer = header.buf.writer(); renderFunctionSignature(&ctx, header, writer, decl) catch |err| { @@ -116,24 +283,6 @@ pub fn generateHeader( } } -fn genArray(file: *C, decl: *Decl) !void { - const tv = decl.typed_value.most_recent.typed_value; - // TODO: prevent inline asm constants from being emitted - const name = try map(file.base.allocator, mem.span(decl.name)); - defer file.base.allocator.free(name); - if (tv.val.cast(Value.Payload.Bytes)) |payload| - if (tv.ty.sentinel()) |sentinel| - if (sentinel.toUnsignedInt() == 0) - // TODO: static by default - try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }) - else - return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{}) - else - return file.fail(decl.src(), "TODO byte arrays without sentinels", .{}) - else - return file.fail(decl.src(), "TODO non-byte arrays", .{}); -} - const Context = struct { decl: *Decl, inst_map: *std.AutoHashMap(*Inst, []u8), @@ -141,6 +290,7 @@ const Context = struct { argdex: usize = 0, unnamed_index: usize = 0, error_msg: *Compilation.ErrorMsg = undefined, + target: std.Target, fn resolveInst(self: *Context, inst: *Inst) ![]u8 { if (inst.cast(Inst.Constant)) |const_inst| { @@ -170,55 +320,6 @@ const Context = struct { } }; -fn genFn(file: *C, decl: *Decl) !void { - const writer = file.main.writer(); - const tv = decl.typed_value.most_recent.typed_value; - - var arena = std.heap.ArenaAllocator.init(file.base.allocator); - defer arena.deinit(); - var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); - defer inst_map.deinit(); - var ctx = Context{ - .decl = decl, - .arena = &arena, - .inst_map = &inst_map, - }; - defer { - file.error_msg = ctx.error_msg; - ctx.deinit(); - } - - try renderFunctionSignature(&ctx, &file.header, writer, decl); - - try writer.writeAll(" {"); - - const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; - const instructions = func.analysis.success.instructions; - if (instructions.len > 0) { - try writer.writeAll("\n"); - for (instructions) |inst| { - if (switch (inst.tag) { - .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), - .call => try genCall(&ctx, file, inst.castTag(.call).?), - .add => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "+"), - .sub => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "-"), - .ret => try genRet(&ctx, inst.castTag(.ret).?), - .retvoid => try genRetVoid(file), - .arg => try genArg(&ctx), - .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), - .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?), - .unreach => try genUnreach(file, inst.castTag(.unreach).?), - .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), - else => |e| return ctx.fail(decl.src(), "TODO implement C codegen for {}", .{e}), - }) |name| { - try ctx.inst_map.putNoClobber(inst, name); - } - } - } - - try writer.writeAll("}\n\n"); -} - fn genArg(ctx: *Context) !?[]u8 { const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{}", .{ctx.argdex}); ctx.argdex += 1; @@ -226,12 +327,24 @@ fn genArg(ctx: *Context) !?[]u8 { } fn genRetVoid(file: *C) !?[]u8 { - try file.main.writer().print(indentation ++ "return;\n", .{}); + try file.main.writer().print("return;\n", .{}); return null; } -fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 { - return ctx.fail(ctx.decl.src(), "TODO return", .{}); +fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { + const writer = file.main.writer(); + try writer.writeAll("return "); + try genValue(ctx, writer, inst.operand); + try writer.writeAll(";\n"); + return null; +} + +fn genValue(ctx: *Context, writer: Writer, inst: *Inst) !void { + if (inst.value()) |val| { + try renderValue(ctx, writer, inst.ty, val); + return; + } + return ctx.fail(ctx.decl.src(), "TODO: C backend: genValue for non-constant value", .{}); } fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { @@ -241,7 +354,7 @@ fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { const writer = file.main.writer(); const name = try ctx.name(); const from = try ctx.resolveInst(inst.operand); - try writer.writeAll(indentation ++ "const "); + try writer.writeAll("const "); try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print(" {} = (", .{name}); try renderType(ctx, &file.header, writer, inst.base.ty); @@ -256,7 +369,7 @@ fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []con const rhs = ctx.resolveInst(inst.rhs); const writer = file.main.writer(); const name = try ctx.name(); - try writer.writeAll(indentation ++ "const "); + try writer.writeAll("const "); try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs }); return name; @@ -265,41 +378,42 @@ fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []con fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { const writer = file.main.writer(); const header = file.header.buf.writer(); - try writer.writeAll(indentation); if (inst.func.castTag(.constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const target = func_val.func.owner_decl; - const target_ty = target.typed_value.most_recent.typed_value.ty; - const ret_ty = target_ty.fnReturnType().tag(); - if (target_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) { - try writer.print("(void)", .{}); - } - const tname = mem.spanZ(target.name); - if (file.called.get(tname) == null) { - try file.called.put(tname, void{}); - try renderFunctionSignature(ctx, &file.header, header, target); - try header.writeAll(";\n"); - } - try writer.print("{}(", .{tname}); - if (inst.args.len != 0) { - for (inst.args) |arg, i| { - if (i > 0) { - try writer.writeAll(", "); - } - if (arg.cast(Inst.Constant)) |con| { - try renderValue(ctx, writer, arg.ty, con.val); - } else { - const val = try ctx.resolveInst(arg); - try writer.print("{}", .{val}); - } + const fn_decl = if (func_inst.val.cast(Value.Payload.ExternFn)) |extern_fn| + extern_fn.decl + else if (func_inst.val.cast(Value.Payload.Function)) |func_val| + func_val.func.owner_decl + else + unreachable; + + const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty; + const ret_ty = fn_ty.fnReturnType().tag(); + if (fn_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) { + try writer.print("(void)", .{}); + } + const fn_name = mem.spanZ(fn_decl.name); + if (file.called.get(fn_name) == null) { + try file.called.put(fn_name, void{}); + try renderFunctionSignature(ctx, &file.header, header, fn_decl); + try header.writeAll(";\n"); + } + try writer.print("{s}(", .{fn_name}); + if (inst.args.len != 0) { + for (inst.args) |arg, i| { + if (i > 0) { + try writer.writeAll(", "); + } + if (arg.cast(Inst.Constant)) |con| { + try renderValue(ctx, writer, arg.ty, con.val); + } else { + const val = try ctx.resolveInst(arg); + try writer.print("{}", .{val}); } } - try writer.writeAll(");\n"); - } else { - return ctx.fail(ctx.decl.src(), "TODO non-function call target?", .{}); } + try writer.writeAll(");\n"); } else { - return ctx.fail(ctx.decl.src(), "TODO non-constant call inst?", .{}); + return ctx.fail(ctx.decl.src(), "TODO: C backend: implement function pointers", .{}); } return null; } @@ -315,13 +429,12 @@ fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { } fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { - try file.main.writer().writeAll(indentation ++ "zig_unreachable();\n"); + try file.main.writer().writeAll("zig_unreachable();\n"); return null; } fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { const writer = file.main.writer(); - try writer.writeAll(indentation); for (as.inputs) |i, index| { if (i[0] == '{' and i[i.len - 1] == '}') { const reg = i[1 .. i.len - 1]; diff --git a/src/link/C.zig b/src/link/C.zig index 325f2ee048..62a350826e 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -15,8 +15,6 @@ pub const base_tag: File.Tag = .c; pub const Header = struct { buf: std.ArrayList(u8), - need_stddef: bool = false, - need_stdint: bool = false, emit_loc: ?Compilation.EmitLoc, pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header { @@ -31,20 +29,8 @@ pub const Header = struct { defer tracy.end(); try writer.writeAll(@embedFile("cbe.h")); - var includes = false; - if (self.need_stddef) { - try writer.writeAll("#include \n"); - includes = true; - } - if (self.need_stdint) { - try writer.writeAll("#include \n"); - includes = true; - } - if (includes) { - try writer.writeByte('\n'); - } if (self.buf.items.len > 0) { - try writer.print("{}", .{self.buf.items}); + try writer.print("{s}", .{self.buf.items}); } } diff --git a/src/link/cbe.h b/src/link/cbe.h index c3f889c6f1..cd37ba2f2e 100644 --- a/src/link/cbe.h +++ b/src/link/cbe.h @@ -23,3 +23,7 @@ #define zig_unreachable() #endif +#include +#define int128_t __int128 +#define uint128_t unsigned __int128 + diff --git a/src/type.zig b/src/type.zig index f3ca23014d..f08408738a 100644 --- a/src/type.zig +++ b/src/type.zig @@ -172,7 +172,15 @@ pub const Type = extern union { const is_slice_b = isSlice(b); if (is_slice_a != is_slice_b) return false; - @panic("TODO implement more pointer Type equality comparison"); + + const ptr_size_a = ptrSize(a); + const ptr_size_b = ptrSize(b); + if (ptr_size_a != ptr_size_b) + return false; + + std.debug.panic("TODO implement more pointer Type equality comparison: {} and {}", .{ + a, b, + }); }, .Int => { // Detect that e.g. u64 != usize, even if the bits match on a particular target. @@ -1128,6 +1136,88 @@ pub const Type = extern union { }; } + /// Asserts the `Type` is a pointer. + pub fn ptrSize(self: Type) std.builtin.TypeInfo.Pointer.Size { + return switch (self.tag()) { + .u8, + .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .f16, + .f32, + .f64, + .f128, + .c_void, + .bool, + .void, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .noreturn, + .@"null", + .@"undefined", + .array, + .array_sentinel, + .array_u8, + .array_u8_sentinel_0, + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + .function, + .int_unsigned, + .int_signed, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, + .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, + .empty_struct, + => unreachable, + + .const_slice, + .mut_slice, + .const_slice_u8, + => .Slice, + + .many_const_pointer, + .many_mut_pointer, + => .Many, + + .c_const_pointer, + .c_mut_pointer, + => .C, + + .single_const_pointer, + .single_mut_pointer, + .single_const_pointer_to_comptime_int, + => .One, + + .pointer => self.cast(Payload.Pointer).?.size, + }; + } + pub fn isSlice(self: Type) bool { return switch (self.tag()) { .u8, diff --git a/src/value.zig b/src/value.zig index 4271ae66f4..53c4b2d540 100644 --- a/src/value.zig +++ b/src/value.zig @@ -82,6 +82,7 @@ pub const Value = extern union { int_big_positive, int_big_negative, function, + extern_fn, variable, ref_val, decl_ref, @@ -205,6 +206,7 @@ pub const Value = extern union { @panic("TODO implement copying of big ints"); }, .function => return self.copyPayloadShallow(allocator, Payload.Function), + .extern_fn => return self.copyPayloadShallow(allocator, Payload.ExternFn), .variable => return self.copyPayloadShallow(allocator, Payload.Variable), .ref_val => { const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise); @@ -337,6 +339,7 @@ pub const Value = extern union { .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}), .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}), .function => return out_stream.writeAll("(function)"), + .extern_fn => return out_stream.writeAll("(extern function)"), .variable => return out_stream.writeAll("(variable)"), .ref_val => { const ref_val = val.cast(Payload.RefVal).?; @@ -468,6 +471,7 @@ pub const Value = extern union { .int_big_positive, .int_big_negative, .function, + .extern_fn, .variable, .ref_val, .decl_ref, @@ -533,6 +537,7 @@ pub const Value = extern union { .anyframe_type, .null_value, .function, + .extern_fn, .variable, .ref_val, .decl_ref, @@ -617,6 +622,7 @@ pub const Value = extern union { .anyframe_type, .null_value, .function, + .extern_fn, .variable, .ref_val, .decl_ref, @@ -701,6 +707,7 @@ pub const Value = extern union { .anyframe_type, .null_value, .function, + .extern_fn, .variable, .ref_val, .decl_ref, @@ -812,6 +819,7 @@ pub const Value = extern union { .anyframe_type, .null_value, .function, + .extern_fn, .variable, .ref_val, .decl_ref, @@ -901,6 +909,7 @@ pub const Value = extern union { .anyframe_type, .null_value, .function, + .extern_fn, .variable, .ref_val, .decl_ref, @@ -1071,6 +1080,7 @@ pub const Value = extern union { .bool_false, .null_value, .function, + .extern_fn, .variable, .ref_val, .decl_ref, @@ -1150,6 +1160,7 @@ pub const Value = extern union { .anyframe_type, .null_value, .function, + .extern_fn, .variable, .ref_val, .decl_ref, @@ -1383,6 +1394,10 @@ pub const Value = extern union { const payload = @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise); std.hash.autoHash(&hasher, payload.func); }, + .extern_fn => { + const payload = @fieldParentPtr(Payload.ExternFn, "base", self.ptr_otherwise); + std.hash.autoHash(&hasher, payload.decl); + }, .variable => { const payload = @fieldParentPtr(Payload.Variable, "base", self.ptr_otherwise); std.hash.autoHash(&hasher, payload.variable); @@ -1449,6 +1464,7 @@ pub const Value = extern union { .bool_false, .null_value, .function, + .extern_fn, .variable, .int_u64, .int_i64, @@ -1533,6 +1549,7 @@ pub const Value = extern union { .bool_false, .null_value, .function, + .extern_fn, .variable, .int_u64, .int_i64, @@ -1634,6 +1651,7 @@ pub const Value = extern union { .bool_true, .bool_false, .function, + .extern_fn, .variable, .int_u64, .int_i64, @@ -1730,6 +1748,7 @@ pub const Value = extern union { .bool_true, .bool_false, .function, + .extern_fn, .variable, .int_u64, .int_i64, @@ -1793,6 +1812,11 @@ pub const Value = extern union { func: *Module.Fn, }; + pub const ExternFn = struct { + base: Payload = Payload{ .tag = .extern_fn }, + decl: *Module.Decl, + }; + pub const Variable = struct { base: Payload = Payload{ .tag = .variable }, variable: *Module.Var, -- cgit v1.2.3 From 52056b156b8e8ed848c909e527f0c89435e24deb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 28 Dec 2020 17:46:50 -0700 Subject: stage2: C backend: pointer cast decl refs if necessary --- src/codegen/c.zig | 51 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 8d706f8735..a568e4df45 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -11,6 +11,7 @@ const Type = @import("../type.zig").Type; const C = link.File.C; const Decl = Module.Decl; const mem = std.mem; +const log = std.log.scoped(.c); const Writer = std.ArrayList(u8).Writer; @@ -22,7 +23,6 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { fn renderType( ctx: *Context, - header: *C.Header, writer: Writer, t: Type, ) error{ OutOfMemory, AnalysisFail }!void { @@ -80,12 +80,12 @@ fn renderType( if (t.isVolatilePtr()) { try writer.writeAll("volatile "); } - try renderType(ctx, header, writer, t.elemType()); + try renderType(ctx, writer, t.elemType()); try writer.writeAll(" *"); } }, .Array => { - try renderType(ctx, header, writer, t.elemType()); + try renderType(ctx, writer, t.elemType()); const sentinel_bit = @boolToInt(t.sentinel() != null); const c_len = t.arrayLen() + sentinel_bit; try writer.print("[{d}]", .{c_len}); @@ -113,7 +113,16 @@ fn renderValue( .one => try writer.writeAll("1"), .decl_ref => { const decl_ref_payload = val.cast(Value.Payload.DeclRef).?; - try writer.print("&{s}", .{decl_ref_payload.decl.name}); + + // Determine if we must pointer cast. + const decl_tv = decl_ref_payload.decl.typed_value.most_recent.typed_value; + if (t.eql(decl_tv.ty)) { + try writer.print("&{s}", .{decl_ref_payload.decl.name}); + } else { + try writer.writeAll("("); + try renderType(ctx, writer, t); + try writer.print(")&{s}", .{decl_ref_payload.decl.name}); + } }, .function => { const payload = val.cast(Value.Payload.Function).?; @@ -155,12 +164,11 @@ fn renderValue( fn renderFunctionSignature( ctx: *Context, - header: *C.Header, writer: Writer, decl: *Decl, ) !void { const tv = decl.typed_value.most_recent.typed_value; - try renderType(ctx, header, writer, tv.ty.fnReturnType()); + try renderType(ctx, writer, tv.ty.fnReturnType()); // Use the child allocator directly, as we know the name can be freed before // the rest of the arena. const name = try map(ctx.arena.child_allocator, mem.spanZ(decl.name)); @@ -175,7 +183,7 @@ fn renderFunctionSignature( if (index > 0) { try writer.writeAll(", "); } - try renderType(ctx, header, writer, tv.ty.fnParamType(index)); + try renderType(ctx, writer, tv.ty.fnParamType(index)); try writer.print(" arg{}", .{index}); } } @@ -194,6 +202,7 @@ pub fn generate(file: *C, decl: *Decl) !void { .arena = &arena, .inst_map = &inst_map, .target = file.base.options.target, + .header = &file.header, }; defer { file.error_msg = ctx.error_msg; @@ -202,7 +211,7 @@ pub fn generate(file: *C, decl: *Decl) !void { if (tv.val.cast(Value.Payload.Function)) |func_payload| { const writer = file.main.writer(); - try renderFunctionSignature(&ctx, &file.header, writer, decl); + try renderFunctionSignature(&ctx, writer, decl); try writer.writeAll(" {"); @@ -211,9 +220,11 @@ pub fn generate(file: *C, decl: *Decl) !void { if (instructions.len > 0) { try writer.writeAll("\n"); for (instructions) |inst| { - const indent_size = 4; - const indent_level = 1; - try writer.writeByteNTimes(' ', indent_size * indent_level); + if (inst.tag != .dbg_stmt) { + const indent_size = 4; + const indent_level = 1; + try writer.writeByteNTimes(' ', indent_size * indent_level); + } if (switch (inst.tag) { .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), .call => try genCall(&ctx, file, inst.castTag(.call).?), @@ -226,7 +237,7 @@ pub fn generate(file: *C, decl: *Decl) !void { .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?), .unreach => try genUnreach(file, inst.castTag(.unreach).?), .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), - else => |e| return ctx.fail(decl.src(), "TODO implement C codegen for {}", .{e}), + else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }) |name| { try ctx.inst_map.putNoClobber(inst, name); } @@ -243,7 +254,7 @@ pub fn generate(file: *C, decl: *Decl) !void { // TODO ask the Decl if it is const // https://github.com/ziglang/zig/issues/7582 - try renderType(&ctx, &file.header, writer, tv.ty); + try renderType(&ctx, writer, tv.ty); try writer.print(" {s} = ", .{decl.name}); try renderValue(&ctx, writer, tv.ty, tv.val); try writer.writeAll(";\n"); @@ -269,9 +280,10 @@ pub fn generateHeader( .arena = &arena, .inst_map = &inst_map, .target = comp.getTarget(), + .header = header, }; const writer = header.buf.writer(); - renderFunctionSignature(&ctx, header, writer, decl) catch |err| { + renderFunctionSignature(&ctx, writer, decl) catch |err| { if (err == error.AnalysisFail) { try module.failed_decls.put(module.gpa, decl, ctx.error_msg); } @@ -291,6 +303,7 @@ const Context = struct { unnamed_index: usize = 0, error_msg: *Compilation.ErrorMsg = undefined, target: std.Target, + header: *C.Header, fn resolveInst(self: *Context, inst: *Inst) ![]u8 { if (inst.cast(Inst.Constant)) |const_inst| { @@ -355,9 +368,9 @@ fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { const name = try ctx.name(); const from = try ctx.resolveInst(inst.operand); try writer.writeAll("const "); - try renderType(ctx, &file.header, writer, inst.base.ty); + try renderType(ctx, writer, inst.base.ty); try writer.print(" {} = (", .{name}); - try renderType(ctx, &file.header, writer, inst.base.ty); + try renderType(ctx, writer, inst.base.ty); try writer.print("){};\n", .{from}); return name; } @@ -370,7 +383,7 @@ fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []con const writer = file.main.writer(); const name = try ctx.name(); try writer.writeAll("const "); - try renderType(ctx, &file.header, writer, inst.base.ty); + try renderType(ctx, writer, inst.base.ty); try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs }); return name; } @@ -394,7 +407,7 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { const fn_name = mem.spanZ(fn_decl.name); if (file.called.get(fn_name) == null) { try file.called.put(fn_name, void{}); - try renderFunctionSignature(ctx, &file.header, header, fn_decl); + try renderFunctionSignature(ctx, header, fn_decl); try header.writeAll(";\n"); } try writer.print("{s}(", .{fn_name}); @@ -440,7 +453,7 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { const reg = i[1 .. i.len - 1]; const arg = as.args[index]; try writer.writeAll("register "); - try renderType(ctx, &file.header, writer, arg.ty); + try renderType(ctx, writer, arg.ty); try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg }); // TODO merge constant handling into inst_map as well if (arg.castTag(.constant)) |c| { -- cgit v1.2.3 From 37f04d66be014291303b7d8ba49ff4232dbdb696 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 28 Dec 2020 18:24:55 -0700 Subject: stage2: C backend: properly render type of array decls --- src/codegen/c.zig | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a568e4df45..3311a9a25e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -86,9 +86,7 @@ fn renderType( }, .Array => { try renderType(ctx, writer, t.elemType()); - const sentinel_bit = @boolToInt(t.sentinel() != null); - const c_len = t.arrayLen() + sentinel_bit; - try writer.print("[{d}]", .{c_len}); + try writer.writeAll(" *"); }, else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement type {s}", .{ @tagName(e), @@ -254,8 +252,21 @@ pub fn generate(file: *C, decl: *Decl) !void { // TODO ask the Decl if it is const // https://github.com/ziglang/zig/issues/7582 - try renderType(&ctx, writer, tv.ty); - try writer.print(" {s} = ", .{decl.name}); + var suffix = std.ArrayList(u8).init(file.base.allocator); + defer suffix.deinit(); + + var render_ty = tv.ty; + while (render_ty.zigTypeTag() == .Array) { + const sentinel_bit = @boolToInt(render_ty.sentinel() != null); + const c_len = render_ty.arrayLen() + sentinel_bit; + try suffix.writer().print("[{d}]", .{c_len}); + render_ty = render_ty.elemType(); + } + + try renderType(&ctx, writer, render_ty); + try writer.print(" {s}{s}", .{ decl.name, suffix.items }); + + try writer.writeAll(" = "); try renderValue(&ctx, writer, tv.ty, tv.val); try writer.writeAll(";\n"); } -- cgit v1.2.3 From a54ccd85374407a5015c5d8e0173089e75da9be4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 28 Dec 2020 18:43:01 -0700 Subject: stage2: C backend: implement `@breakpoint` and clean up test harness --- src/codegen/c.zig | 6 +++--- src/link/cbe.h | 19 +++++++++++++++++-- src/test.zig | 36 ++++-------------------------------- test/stage2/cbe.zig | 51 ++++++++++++++++++++++++--------------------------- 4 files changed, 48 insertions(+), 64 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 3311a9a25e..d949591a49 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -232,7 +232,7 @@ pub fn generate(file: *C, decl: *Decl) !void { .retvoid => try genRetVoid(file), .arg => try genArg(&ctx), .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), - .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?), + .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), .unreach => try genUnreach(file, inst.castTag(.unreach).?), .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}), @@ -447,8 +447,8 @@ fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { return null; } -fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { - // TODO ?? +fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 { + try file.main.writer().writeAll("zig_breakpoint();\n"); return null; } diff --git a/src/link/cbe.h b/src/link/cbe.h index cd37ba2f2e..e62e6766ef 100644 --- a/src/link/cbe.h +++ b/src/link/cbe.h @@ -1,5 +1,4 @@ #if __STDC_VERSION__ >= 199901L -// C99 or newer #include #else #define bool unsigned char @@ -17,12 +16,28 @@ #define zig_noreturn #endif -#if __GNUC__ +#if defined(__GNUC__) #define zig_unreachable() __builtin_unreachable() #else #define zig_unreachable() #endif +#if defined(_MSC_VER) +#define zig_breakpoint __debugbreak() +#else +#if defined(__MINGW32__) || defined(__MINGW64__) +#define zig_breakpoint __debugbreak() +#elif defined(__clang__) +#define zig_breakpoint __builtin_debugtrap() +#elif defined(__GNUC__) +#define zig_breakpoint __builtin_trap() +#elif defined(__i386__) || defined(__x86_64__) +#define zig_breakpoint __asm__ volatile("int $0x03"); +#else +#define zig_breakpoint raise(SIGTRAP) +#endif +#endif + #include #define int128_t __int128 #define uint128_t unsigned __int128 diff --git a/src/test.zig b/src/test.zig index 6deee347af..f4374ea0bd 100644 --- a/src/test.zig +++ b/src/test.zig @@ -646,35 +646,16 @@ pub const TestContext = struct { defer file.close(); var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read headeroutput!"); - if (expected_output.len != out.len) { - std.debug.print("\nTransformed header length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); - std.process.exit(1); - } - for (expected_output) |e, i| { - if (out[i] != e) { - std.debug.print("\nTransformed header differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); - std.process.exit(1); - } - } + std.testing.expectEqualStrings(expected_output, out); }, .Transformation => |expected_output| { if (case.cbe) { // The C file is always closed after an update, because we don't support - // incremental updates + // incremental updates. var file = try tmp.dir.openFile(bin_name, .{ .read = true }); defer file.close(); var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read C output!"); - - if (expected_output.len != out.len) { - std.debug.print("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); - std.process.exit(1); - } - for (expected_output) |e, i| { - if (out[i] != e) { - std.debug.print("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); - std.process.exit(1); - } - } + std.testing.expectEqualStrings(expected_output, out); } else { update_node.setEstimatedTotalItems(5); var emit_node = update_node.start("emit", 0); @@ -694,16 +675,7 @@ pub const TestContext = struct { test_node.activate(); defer test_node.end(); - if (expected_output.len != out_zir.items.len) { - std.debug.print("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); - std.process.exit(1); - } - for (expected_output) |e, i| { - if (out_zir.items[i] != e) { - std.debug.print("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); - std.process.exit(1); - } - } + std.testing.expectEqualStrings(expected_output, out_zir.items); } }, .Error => |e| { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 96db7b835e..3a9bff897a 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -15,6 +15,7 @@ pub fn addCases(ctx: *TestContext) !void { \\} , \\zig_noreturn void _start(void) { + \\ zig_breakpoint(); \\ zig_unreachable(); \\} \\ @@ -41,6 +42,7 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ \\zig_noreturn void main(void) { + \\ zig_breakpoint(); \\ zig_unreachable(); \\} \\ @@ -61,8 +63,6 @@ pub fn addCases(ctx: *TestContext) !void { \\ exitGood(); \\} , - \\#include - \\ \\zig_noreturn void exitGood(void); \\ \\const char *const exitGood__anon_0 = "{rax}"; @@ -74,9 +74,10 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ \\zig_noreturn void exitGood(void) { - \\ register size_t rax_constant __asm__("rax") = 231; - \\ register size_t rdi_constant __asm__("rdi") = 0; + \\ register uintptr_t rax_constant __asm__("rax") = 231; + \\ register uintptr_t rdi_constant __asm__("rdi") = 0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\ zig_breakpoint(); \\ zig_unreachable(); \\} \\ @@ -96,9 +97,7 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\#include - \\ - \\zig_noreturn void exit(size_t arg0); + \\zig_noreturn void exit(uintptr_t arg0); \\ \\const char *const exit__anon_0 = "{rax}"; \\const char *const exit__anon_1 = "{rdi}"; @@ -108,10 +107,11 @@ pub fn addCases(ctx: *TestContext) !void { \\ exit(0); \\} \\ - \\zig_noreturn void exit(size_t arg0) { - \\ register size_t rax_constant __asm__("rax") = 231; - \\ register size_t rdi_constant __asm__("rdi") = arg0; + \\zig_noreturn void exit(uintptr_t arg0) { + \\ register uintptr_t rax_constant __asm__("rax") = 231; + \\ register uintptr_t rdi_constant __asm__("rdi") = arg0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\ zig_breakpoint(); \\ zig_unreachable(); \\} \\ @@ -131,7 +131,6 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\#include \\#include \\ \\zig_noreturn void exit(uint8_t arg0); @@ -145,10 +144,11 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ \\zig_noreturn void exit(uint8_t arg0) { - \\ const size_t __temp_0 = (size_t)arg0; - \\ register size_t rax_constant __asm__("rax") = 231; - \\ register size_t rdi_constant __asm__("rdi") = __temp_0; + \\ const uintptr_t __temp_0 = (uintptr_t)arg0; + \\ register uintptr_t rax_constant __asm__("rax") = 231; + \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\ zig_breakpoint(); \\ zig_unreachable(); \\} \\ @@ -172,7 +172,6 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\#include \\#include \\ \\zig_noreturn void exitMath(uint8_t arg0); @@ -193,10 +192,11 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ \\zig_noreturn void exit(uint8_t arg0) { - \\ const size_t __temp_0 = (size_t)arg0; - \\ register size_t rax_constant __asm__("rax") = 231; - \\ register size_t rdi_constant __asm__("rdi") = __temp_0; + \\ const uintptr_t __temp_0 = (uintptr_t)arg0; + \\ register uintptr_t rax_constant __asm__("rax") = 231; + \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\ zig_breakpoint(); \\ zig_unreachable(); \\} \\ @@ -220,7 +220,6 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\#include \\#include \\ \\zig_noreturn void exitMath(uint8_t arg0); @@ -241,10 +240,11 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ \\zig_noreturn void exit(uint8_t arg0) { - \\ const size_t __temp_0 = (size_t)arg0; - \\ register size_t rax_constant __asm__("rax") = 231; - \\ register size_t rdi_constant __asm__("rdi") = __temp_0; + \\ const uintptr_t __temp_0 = (uintptr_t)arg0; + \\ register uintptr_t rax_constant __asm__("rax") = 231; + \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\ zig_breakpoint(); \\ zig_unreachable(); \\} \\ @@ -276,9 +276,7 @@ pub fn addCases(ctx: *TestContext) !void { ctx.h("header with usize param function", linux_x64, \\export fn start(a: usize) void{} , - \\#include - \\ - \\void start(size_t arg0); + \\void start(uintptr_t arg0); \\ ); ctx.h("header with bool param function", linux_x64, @@ -308,10 +306,9 @@ pub fn addCases(ctx: *TestContext) !void { ctx.h("header with multiple includes", linux_x64, \\export fn start(a: u32, b: usize) void{} , - \\#include \\#include \\ - \\void start(uint32_t arg0, size_t arg1); + \\void start(uint32_t arg0, uintptr_t arg1); \\ ); } -- cgit v1.2.3 From bbe66572e1c1e2abff0a433c36291f3429482e8f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 28 Dec 2020 20:15:00 -0700 Subject: stage2: C backend: handle string literals more gracefully --- src/codegen/c.zig | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index d949591a49..6f00992327 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -137,22 +137,32 @@ fn renderValue( ), }, .Array => { - // TODO first try specific tag representations for more efficiency - // Fall back to inefficient generic implementation. - try writer.writeAll("{"); - var index: usize = 0; - const len = t.arrayLen(); - const elem_ty = t.elemType(); - while (index < len) : (index += 1) { - if (index != 0) try writer.writeAll(","); - const elem_val = try val.elemValue(&ctx.arena.allocator, index); - try renderValue(ctx, writer, elem_ty, elem_val); - } - if (t.sentinel()) |sentinel_val| { - if (index != 0) try writer.writeAll(","); - try renderValue(ctx, writer, elem_ty, sentinel_val); + // First try specific tag representations for more efficiency. + switch (val.tag()) { + .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), + .bytes => { + const bytes = val.cast(Value.Payload.Bytes).?.data; + // TODO: make our own C string escape instead of using {Z} + try writer.print("\"{Z}\"", .{bytes}); + }, + else => { + // Fall back to generic implementation. + try writer.writeAll("{"); + var index: usize = 0; + const len = t.arrayLen(); + const elem_ty = t.elemType(); + while (index < len) : (index += 1) { + if (index != 0) try writer.writeAll(","); + const elem_val = try val.elemValue(&ctx.arena.allocator, index); + try renderValue(ctx, writer, elem_ty, elem_val); + } + if (t.sentinel()) |sentinel_val| { + if (index != 0) try writer.writeAll(","); + try renderValue(ctx, writer, elem_ty, sentinel_val); + } + try writer.writeAll("}"); + }, } - try writer.writeAll("}"); }, else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{ @tagName(e), -- cgit v1.2.3 From 813d3308ccd13bdc96a40b583ffd8722651b7b83 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 28 Dec 2020 20:27:58 -0700 Subject: stage2: update C backend test cases for new output --- src/codegen/c.zig | 20 +++++++++++++++----- test/stage2/cbe.zig | 45 ++++++++++++++++----------------------------- 2 files changed, 31 insertions(+), 34 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 6f00992327..364aa4d7ef 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -198,6 +198,13 @@ fn renderFunctionSignature( try writer.writeByte(')'); } +fn indent(file: *C) !void { + const indent_size = 4; + const indent_level = 1; + const indent_amt = indent_size * indent_level; + try file.main.writer().writeByteNTimes(' ', indent_amt); +} + pub fn generate(file: *C, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; @@ -228,11 +235,6 @@ pub fn generate(file: *C, decl: *Decl) !void { if (instructions.len > 0) { try writer.writeAll("\n"); for (instructions) |inst| { - if (inst.tag != .dbg_stmt) { - const indent_size = 4; - const indent_level = 1; - try writer.writeByteNTimes(' ', indent_size * indent_level); - } if (switch (inst.tag) { .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), .call => try genCall(&ctx, file, inst.castTag(.call).?), @@ -361,11 +363,13 @@ fn genArg(ctx: *Context) !?[]u8 { } fn genRetVoid(file: *C) !?[]u8 { + try indent(file); try file.main.writer().print("return;\n", .{}); return null; } fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { + try indent(file); const writer = file.main.writer(); try writer.writeAll("return "); try genValue(ctx, writer, inst.operand); @@ -384,6 +388,7 @@ fn genValue(ctx: *Context, writer: Writer, inst: *Inst) !void { fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { if (inst.base.isUnused()) return null; + try indent(file); const op = inst.operand; const writer = file.main.writer(); const name = try ctx.name(); @@ -399,6 +404,7 @@ fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { if (inst.base.isUnused()) return null; + try indent(file); const lhs = ctx.resolveInst(inst.lhs); const rhs = ctx.resolveInst(inst.rhs); const writer = file.main.writer(); @@ -410,6 +416,7 @@ fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []con } fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { + try indent(file); const writer = file.main.writer(); const header = file.header.buf.writer(); if (inst.func.castTag(.constant)) |func_inst| { @@ -458,16 +465,19 @@ fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { } fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 { + try indent(file); try file.main.writer().writeAll("zig_breakpoint();\n"); return null; } fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { + try indent(file); try file.main.writer().writeAll("zig_unreachable();\n"); return null; } fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { + try indent(file); const writer = file.main.writer(); for (as.inputs) |i, index| { if (i[0] == '{' and i[i.len - 1] == '}') { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 2212d48298..cd26b8aa58 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -23,6 +23,7 @@ pub fn addCases(ctx: *TestContext) !void { // Now change the message only // TODO fix C backend not supporting updates + // https://github.com/ziglang/zig/issues/7589 //case.addCompareOutput( // \\extern fn puts(s: [*:0]const u8) c_int; // \\export fn main() c_int { @@ -88,9 +89,9 @@ pub fn addCases(ctx: *TestContext) !void { , \\zig_noreturn void exitGood(void); \\ - \\const char *const exitGood__anon_0 = "{rax}"; - \\const char *const exitGood__anon_1 = "{rdi}"; - \\const char *const exitGood__anon_2 = "syscall"; + \\static uint8_t exitGood__anon_0[6] = "{rax}"; + \\static uint8_t exitGood__anon_1[6] = "{rdi}"; + \\static uint8_t exitGood__anon_2[8] = "syscall"; \\ \\zig_noreturn void _start(void) { \\ exitGood(); @@ -122,9 +123,9 @@ pub fn addCases(ctx: *TestContext) !void { , \\zig_noreturn void exit(uintptr_t arg0); \\ - \\const char *const exit__anon_0 = "{rax}"; - \\const char *const exit__anon_1 = "{rdi}"; - \\const char *const exit__anon_2 = "syscall"; + \\static uint8_t exit__anon_0[6] = "{rax}"; + \\static uint8_t exit__anon_1[6] = "{rdi}"; + \\static uint8_t exit__anon_2[8] = "syscall"; \\ \\zig_noreturn void _start(void) { \\ exit(0); @@ -154,13 +155,11 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\#include - \\ \\zig_noreturn void exit(uint8_t arg0); \\ - \\const char *const exit__anon_0 = "{rax}"; - \\const char *const exit__anon_1 = "{rdi}"; - \\const char *const exit__anon_2 = "syscall"; + \\static uint8_t exit__anon_0[6] = "{rax}"; + \\static uint8_t exit__anon_1[6] = "{rdi}"; + \\static uint8_t exit__anon_2[8] = "syscall"; \\ \\zig_noreturn void _start(void) { \\ exit(0); @@ -195,14 +194,12 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\#include - \\ \\zig_noreturn void exitMath(uint8_t arg0); \\zig_noreturn void exit(uint8_t arg0); \\ - \\const char *const exit__anon_0 = "{rax}"; - \\const char *const exit__anon_1 = "{rdi}"; - \\const char *const exit__anon_2 = "syscall"; + \\static uint8_t exit__anon_0[6] = "{rax}"; + \\static uint8_t exit__anon_1[6] = "{rdi}"; + \\static uint8_t exit__anon_2[8] = "syscall"; \\ \\zig_noreturn void _start(void) { \\ exitMath(1); @@ -243,14 +240,12 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\#include - \\ \\zig_noreturn void exitMath(uint8_t arg0); \\zig_noreturn void exit(uint8_t arg0); \\ - \\const char *const exit__anon_0 = "{rax}"; - \\const char *const exit__anon_1 = "{rdi}"; - \\const char *const exit__anon_2 = "syscall"; + \\static uint8_t exit__anon_0[6] = "{rax}"; + \\static uint8_t exit__anon_1[6] = "{rdi}"; + \\static uint8_t exit__anon_2[8] = "syscall"; \\ \\zig_noreturn void _start(void) { \\ exitMath(1); @@ -275,24 +270,18 @@ pub fn addCases(ctx: *TestContext) !void { ctx.h("header with single param function", linux_x64, \\export fn start(a: u8) void{} , - \\#include - \\ \\void start(uint8_t arg0); \\ ); ctx.h("header with multiple param function", linux_x64, \\export fn start(a: u8, b: u8, c: u8) void{} , - \\#include - \\ \\void start(uint8_t arg0, uint8_t arg1, uint8_t arg2); \\ ); ctx.h("header with u32 param function", linux_x64, \\export fn start(a: u32) void{} , - \\#include - \\ \\void start(uint32_t arg0); \\ ); @@ -329,8 +318,6 @@ pub fn addCases(ctx: *TestContext) !void { ctx.h("header with multiple includes", linux_x64, \\export fn start(a: u32, b: usize) void{} , - \\#include - \\ \\void start(uint32_t arg0, uintptr_t arg1); \\ ); -- cgit v1.2.3 From d18b6785bb394955eb092c82818e0214e456aced Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 29 Dec 2020 17:56:30 -0700 Subject: stage2: C backend improvements * Module: improve doc comments * C backend: improve const-correctness * C backend: introduce renderTypeAndName * C backend: put `static` on functions when appropriate * C backend: fix not handling errors in genBinOp * C backend: handle more IR instructions - alloc, store, boolean comparisons, ret_ptr * C backend: call instruction properly stores its result * test harness: ensure execution tests have empty stderr --- src/Module.zig | 1 + src/codegen/c.zig | 185 ++++++++++++++++++++++++++++++++++++---------------- src/link/C.zig | 2 +- src/test.zig | 1 + src/zir_sema.zig | 6 +- test/stage2/cbe.zig | 64 +++++++++++------- 6 files changed, 178 insertions(+), 81 deletions(-) (limited to 'src/codegen') diff --git a/src/Module.zig b/src/Module.zig index dbb845116a..089bc51893 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -37,6 +37,7 @@ root_scope: *Scope, /// It's rare for a decl to be exported, so we save memory by having a sparse map of /// Decl pointers to details about them being exported. /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. +/// The slice is guaranteed to not be empty. decl_exports: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{}, /// We track which export is associated with the given symbol name for quick /// detection of symbol collisions. diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 364aa4d7ef..828061fac6 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -21,6 +21,34 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { return allocator.dupe(u8, name); } +const Mutability = enum { Const, Mut }; + +fn renderTypeAndName( + ctx: *Context, + writer: Writer, + ty: Type, + name: []const u8, + mutability: Mutability, +) error{ OutOfMemory, AnalysisFail }!void { + var suffix = std.ArrayList(u8).init(&ctx.arena.allocator); + + var render_ty = ty; + while (render_ty.zigTypeTag() == .Array) { + const sentinel_bit = @boolToInt(render_ty.sentinel() != null); + const c_len = render_ty.arrayLen() + sentinel_bit; + try suffix.writer().print("[{d}]", .{c_len}); + render_ty = render_ty.elemType(); + } + + try renderType(ctx, writer, render_ty); + + const const_prefix = switch (mutability) { + .Const => "const ", + .Mut => "", + }; + try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items }); +} + fn renderType( ctx: *Context, writer: Writer, @@ -74,14 +102,14 @@ fn renderType( if (t.isSlice()) { return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{}); } else { + try renderType(ctx, writer, t.elemType()); + try writer.writeAll(" *"); if (t.isConstPtr()) { try writer.writeAll("const "); } if (t.isVolatilePtr()) { try writer.writeAll("volatile "); } - try renderType(ctx, writer, t.elemType()); - try writer.writeAll(" *"); } }, .Array => { @@ -176,12 +204,27 @@ fn renderFunctionSignature( decl: *Decl, ) !void { const tv = decl.typed_value.most_recent.typed_value; + // Determine whether the function is globally visible. + const is_global = blk: { + switch (tv.val.tag()) { + .extern_fn => break :blk true, + .function => { + const func = tv.val.cast(Value.Payload.Function).?.func; + break :blk ctx.module.decl_exports.contains(func.owner_decl); + }, + else => unreachable, + } + }; + if (!is_global) { + try writer.writeAll("static "); + } try renderType(ctx, writer, tv.ty.fnReturnType()); // Use the child allocator directly, as we know the name can be freed before // the rest of the arena. - const name = try map(ctx.arena.child_allocator, mem.spanZ(decl.name)); + const decl_name = mem.span(decl.name); + const name = try map(ctx.arena.child_allocator, decl_name); defer ctx.arena.child_allocator.free(name); - try writer.print(" {}(", .{name}); + try writer.print(" {s}(", .{name}); var param_len = tv.ty.fnParamLen(); if (param_len == 0) try writer.writeAll("void") @@ -205,7 +248,7 @@ fn indent(file: *C) !void { try file.main.writer().writeByteNTimes(' ', indent_amt); } -pub fn generate(file: *C, decl: *Decl) !void { +pub fn generate(file: *C, module: *Module, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; var arena = std.heap.ArenaAllocator.init(file.base.allocator); @@ -218,6 +261,7 @@ pub fn generate(file: *C, decl: *Decl) !void { .inst_map = &inst_map, .target = file.base.options.target, .header = &file.header, + .module = module, }; defer { file.error_msg = ctx.error_msg; @@ -236,17 +280,26 @@ pub fn generate(file: *C, decl: *Decl) !void { try writer.writeAll("\n"); for (instructions) |inst| { if (switch (inst.tag) { + .add => try genBinOp(&ctx, file, inst.castTag(.add).?, "+"), + .alloc => try genAlloc(&ctx, file, inst.castTag(.alloc).?), + .arg => try genArg(&ctx), .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), + .block => try genBlock(&ctx, file, inst.castTag(.block).?), + .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), .call => try genCall(&ctx, file, inst.castTag(.call).?), - .add => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "+"), - .sub => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "-"), + .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="), + .cmp_gt => try genBinOp(&ctx, file, inst.castTag(.cmp_gt).?, ">"), + .cmp_gte => try genBinOp(&ctx, file, inst.castTag(.cmp_gte).?, ">="), + .cmp_lt => try genBinOp(&ctx, file, inst.castTag(.cmp_lt).?, "<"), + .cmp_lte => try genBinOp(&ctx, file, inst.castTag(.cmp_lte).?, "<="), + .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="), + .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), + .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), .ret => try genRet(&ctx, file, inst.castTag(.ret).?), .retvoid => try genRetVoid(file), - .arg => try genArg(&ctx), - .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), - .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), + .store => try genStore(&ctx, file, inst.castTag(.store).?), + .sub => try genBinOp(&ctx, file, inst.castTag(.sub).?, "-"), .unreach => try genUnreach(file, inst.castTag(.unreach).?), - .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }) |name| { try ctx.inst_map.putNoClobber(inst, name); @@ -264,19 +317,7 @@ pub fn generate(file: *C, decl: *Decl) !void { // TODO ask the Decl if it is const // https://github.com/ziglang/zig/issues/7582 - var suffix = std.ArrayList(u8).init(file.base.allocator); - defer suffix.deinit(); - - var render_ty = tv.ty; - while (render_ty.zigTypeTag() == .Array) { - const sentinel_bit = @boolToInt(render_ty.sentinel() != null); - const c_len = render_ty.arrayLen() + sentinel_bit; - try suffix.writer().print("[{d}]", .{c_len}); - render_ty = render_ty.elemType(); - } - - try renderType(&ctx, writer, render_ty); - try writer.print(" {s}{s}", .{ decl.name, suffix.items }); + try renderTypeAndName(&ctx, writer, tv.ty, mem.span(decl.name), .Mut); try writer.writeAll(" = "); try renderValue(&ctx, writer, tv.ty, tv.val); @@ -304,6 +345,7 @@ pub fn generateHeader( .inst_map = &inst_map, .target = comp.getTarget(), .header = header, + .module = module, }; const writer = header.buf.writer(); renderFunctionSignature(&ctx, writer, decl) catch |err| { @@ -327,17 +369,15 @@ const Context = struct { error_msg: *Compilation.ErrorMsg = undefined, target: std.Target, header: *C.Header, + module: *Module, fn resolveInst(self: *Context, inst: *Inst) ![]u8 { - if (inst.cast(Inst.Constant)) |const_inst| { + if (inst.value()) |val| { var out = std.ArrayList(u8).init(&self.arena.allocator); - try renderValue(self, out.writer(), inst.ty, const_inst.val); + try renderValue(self, out.writer(), inst.ty, val); return out.toOwnedSlice(); } - if (self.inst_map.get(inst)) |val| { - return val; - } - unreachable; + return self.inst_map.get(inst).?; // Instruction does not dominate all uses! } fn name(self: *Context) ![]u8 { @@ -356,6 +396,27 @@ const Context = struct { } }; +fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 { + const writer = file.main.writer(); + + // First line: the variable used as data storage. + try indent(file); + const local_name = try ctx.name(); + const elem_type = alloc.base.ty.elemType(); + const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut; + try renderTypeAndName(ctx, writer, elem_type, local_name, mutability); + try writer.writeAll(";\n"); + + // Second line: a pointer to it so that we can refer to it as the allocation. + // One line for the variable, one line for the pointer to the variable, which we return. + try indent(file); + const ptr_local_name = try ctx.name(); + try renderTypeAndName(ctx, writer, alloc.base.ty, ptr_local_name, .Const); + try writer.print(" = &{s};\n", .{local_name}); + + return ptr_local_name; +} + fn genArg(ctx: *Context) !?[]u8 { const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{}", .{ctx.argdex}); ctx.argdex += 1; @@ -371,20 +432,10 @@ fn genRetVoid(file: *C) !?[]u8 { fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { try indent(file); const writer = file.main.writer(); - try writer.writeAll("return "); - try genValue(ctx, writer, inst.operand); - try writer.writeAll(";\n"); + try writer.print("return {s};\n", .{try ctx.resolveInst(inst.operand)}); return null; } -fn genValue(ctx: *Context, writer: Writer, inst: *Inst) !void { - if (inst.value()) |val| { - try renderValue(ctx, writer, inst.ty, val); - return; - } - return ctx.fail(ctx.decl.src(), "TODO: C backend: genValue for non-constant value", .{}); -} - fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { if (inst.base.isUnused()) return null; @@ -393,25 +444,34 @@ fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { const writer = file.main.writer(); const name = try ctx.name(); const from = try ctx.resolveInst(inst.operand); - try writer.writeAll("const "); - try renderType(ctx, writer, inst.base.ty); - try writer.print(" {} = (", .{name}); + + try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); + try writer.writeAll(" = ("); try renderType(ctx, writer, inst.base.ty); - try writer.print("){};\n", .{from}); + try writer.print("){s};\n", .{from}); return name; } -fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { +fn genStore(ctx: *Context, file: *C, inst: *Inst.BinOp) !?[]u8 { + // *a = b; + try indent(file); + const writer = file.main.writer(); + const dest_ptr_name = try ctx.resolveInst(inst.lhs); + const src_val_name = try ctx.resolveInst(inst.rhs); + try writer.print("*{s} = {s};\n", .{ dest_ptr_name, src_val_name }); + return null; +} + +fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !?[]u8 { if (inst.base.isUnused()) return null; try indent(file); - const lhs = ctx.resolveInst(inst.lhs); - const rhs = ctx.resolveInst(inst.rhs); + const lhs = try ctx.resolveInst(inst.lhs); + const rhs = try ctx.resolveInst(inst.rhs); const writer = file.main.writer(); const name = try ctx.name(); - try writer.writeAll("const "); - try renderType(ctx, writer, inst.base.ty); - try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs }); + try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); + try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs }); return name; } @@ -428,13 +488,22 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { unreachable; const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty; - const ret_ty = fn_ty.fnReturnType().tag(); - if (fn_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) { - try writer.print("(void)", .{}); + const ret_ty = fn_ty.fnReturnType(); + const unused_result = inst.base.isUnused(); + var result_name: ?[]u8 = null; + if (unused_result) { + if (ret_ty.hasCodeGenBits()) { + try writer.print("(void)", .{}); + } + } else { + const local_name = try ctx.name(); + try renderTypeAndName(ctx, writer, ret_ty, local_name, .Const); + try writer.writeAll(" = "); + result_name = local_name; } const fn_name = mem.spanZ(fn_decl.name); if (file.called.get(fn_name) == null) { - try file.called.put(fn_name, void{}); + try file.called.put(fn_name, {}); try renderFunctionSignature(ctx, header, fn_decl); try header.writeAll(";\n"); } @@ -453,10 +522,10 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { } } try writer.writeAll(");\n"); + return result_name; } else { return ctx.fail(ctx.decl.src(), "TODO: C backend: implement function pointers", .{}); } - return null; } fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { @@ -464,6 +533,10 @@ fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { return null; } +fn genBlock(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 { + return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{}); +} + fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 { try indent(file); try file.main.writer().writeAll("zig_breakpoint();\n"); diff --git a/src/link/C.zig b/src/link/C.zig index 62a350826e..1059e52115 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -90,7 +90,7 @@ pub fn deinit(self: *C) void { } pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { - codegen.generate(self, decl) catch |err| { + codegen.generate(self, module, decl) catch |err| { if (err == error.AnalysisFail) { try module.failed_decls.put(module.gpa, decl, self.error_msg); } diff --git a/src/test.zig b/src/test.zig index 5681468e10..0b210a85e9 100644 --- a/src/test.zig +++ b/src/test.zig @@ -863,6 +863,7 @@ pub const TestContext = struct { }, } std.testing.expectEqualStrings(expected_stdout, exec_result.stdout); + std.testing.expectEqualStrings("", exec_result.stderr); }, } } diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 5700c18578..3a9f511373 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -352,7 +352,11 @@ fn analyzeInstCoerceToPtrElem(mod: *Module, scope: *Scope, inst: *zir.Inst.Coerc } fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - return mod.fail(scope, inst.base.src, "TODO implement analyzeInstRetPtr", .{}); + const b = try mod.requireFunctionBlock(scope, inst.base.src); + const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; + const ret_type = fn_ty.fnReturnType(); + const ptr_type = try mod.simplePtrType(scope, inst.base.src, ret_type, true, .One); + return mod.addNoOp(b, inst.base.src, ptr_type, .alloc); } fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index cd26b8aa58..0d2a6d4aec 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -33,6 +33,24 @@ pub fn addCases(ctx: *TestContext) !void { //, "yo" ++ std.cstr.line_sep); } + { + var case = ctx.exeFromCompiledC("alloc and retptr", .{}); + + case.addCompareOutput( + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\ + \\fn addIndirect(a: i32, b: i32) i32 { + \\ return add(a, b); + \\} + \\ + \\export fn main() c_int { + \\ return addIndirect(1, 2) - 3; + \\} + , ""); + } + ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable; @@ -59,13 +77,13 @@ pub fn addCases(ctx: *TestContext) !void { \\ main(); \\} , - \\zig_noreturn void main(void); + \\static zig_noreturn void main(void); \\ \\zig_noreturn void _start(void) { \\ main(); \\} \\ - \\zig_noreturn void main(void) { + \\static zig_noreturn void main(void) { \\ zig_breakpoint(); \\ zig_unreachable(); \\} @@ -87,7 +105,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ exitGood(); \\} , - \\zig_noreturn void exitGood(void); + \\static zig_noreturn void exitGood(void); \\ \\static uint8_t exitGood__anon_0[6] = "{rax}"; \\static uint8_t exitGood__anon_1[6] = "{rdi}"; @@ -97,7 +115,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ exitGood(); \\} \\ - \\zig_noreturn void exitGood(void) { + \\static zig_noreturn void exitGood(void) { \\ register uintptr_t rax_constant __asm__("rax") = 231; \\ register uintptr_t rdi_constant __asm__("rdi") = 0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); @@ -121,7 +139,7 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\zig_noreturn void exit(uintptr_t arg0); + \\static zig_noreturn void exit(uintptr_t arg0); \\ \\static uint8_t exit__anon_0[6] = "{rax}"; \\static uint8_t exit__anon_1[6] = "{rdi}"; @@ -131,7 +149,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ exit(0); \\} \\ - \\zig_noreturn void exit(uintptr_t arg0) { + \\static zig_noreturn void exit(uintptr_t arg0) { \\ register uintptr_t rax_constant __asm__("rax") = 231; \\ register uintptr_t rdi_constant __asm__("rdi") = arg0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); @@ -155,7 +173,7 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\zig_noreturn void exit(uint8_t arg0); + \\static zig_noreturn void exit(uint8_t arg0); \\ \\static uint8_t exit__anon_0[6] = "{rax}"; \\static uint8_t exit__anon_1[6] = "{rdi}"; @@ -165,8 +183,8 @@ pub fn addCases(ctx: *TestContext) !void { \\ exit(0); \\} \\ - \\zig_noreturn void exit(uint8_t arg0) { - \\ const uintptr_t __temp_0 = (uintptr_t)arg0; + \\static zig_noreturn void exit(uint8_t arg0) { + \\ uintptr_t const __temp_0 = (uintptr_t)arg0; \\ register uintptr_t rax_constant __asm__("rax") = 231; \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); @@ -194,8 +212,8 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\zig_noreturn void exitMath(uint8_t arg0); - \\zig_noreturn void exit(uint8_t arg0); + \\static zig_noreturn void exitMath(uint8_t arg0); + \\static zig_noreturn void exit(uint8_t arg0); \\ \\static uint8_t exit__anon_0[6] = "{rax}"; \\static uint8_t exit__anon_1[6] = "{rdi}"; @@ -205,14 +223,14 @@ pub fn addCases(ctx: *TestContext) !void { \\ exitMath(1); \\} \\ - \\zig_noreturn void exitMath(uint8_t arg0) { - \\ const uint8_t __temp_0 = 0 + arg0; - \\ const uint8_t __temp_1 = __temp_0 - arg0; + \\static zig_noreturn void exitMath(uint8_t arg0) { + \\ uint8_t const __temp_0 = 0 + arg0; + \\ uint8_t const __temp_1 = __temp_0 - arg0; \\ exit(__temp_1); \\} \\ - \\zig_noreturn void exit(uint8_t arg0) { - \\ const uintptr_t __temp_0 = (uintptr_t)arg0; + \\static zig_noreturn void exit(uint8_t arg0) { + \\ uintptr_t const __temp_0 = (uintptr_t)arg0; \\ register uintptr_t rax_constant __asm__("rax") = 231; \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); @@ -240,8 +258,8 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , - \\zig_noreturn void exitMath(uint8_t arg0); - \\zig_noreturn void exit(uint8_t arg0); + \\static zig_noreturn void exitMath(uint8_t arg0); + \\static zig_noreturn void exit(uint8_t arg0); \\ \\static uint8_t exit__anon_0[6] = "{rax}"; \\static uint8_t exit__anon_1[6] = "{rdi}"; @@ -251,14 +269,14 @@ pub fn addCases(ctx: *TestContext) !void { \\ exitMath(1); \\} \\ - \\zig_noreturn void exitMath(uint8_t arg0) { - \\ const uint8_t __temp_0 = arg0 + 0; - \\ const uint8_t __temp_1 = __temp_0 - arg0; + \\static zig_noreturn void exitMath(uint8_t arg0) { + \\ uint8_t const __temp_0 = arg0 + 0; + \\ uint8_t const __temp_1 = __temp_0 - arg0; \\ exit(__temp_1); \\} \\ - \\zig_noreturn void exit(uint8_t arg0) { - \\ const uintptr_t __temp_0 = (uintptr_t)arg0; + \\static zig_noreturn void exit(uint8_t arg0) { + \\ uintptr_t const __temp_0 = (uintptr_t)arg0; \\ register uintptr_t rax_constant __asm__("rax") = 231; \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); -- cgit v1.2.3 From 3f7d9b5fc19e4081236b3b63aebbc80e1b17f5b5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 30 Dec 2020 22:31:56 -0700 Subject: stage2: rework Value Payload layout This is the same as the previous commit but for Value instead of Type. Add `Value.castTag` and note that it is preferable to call than `Value.cast`. This matches other abstractions in the codebase. Added a convenience function `Value.Tag.create` which really cleans up the callsites of creating `Value` objects. `Value` tags can now share payload types. This is in preparation for another improvement that I want to do. --- src/Compilation.zig | 11 +- src/Module.zig | 193 +++++++---------- src/astgen.zig | 26 +-- src/codegen.zig | 52 ++--- src/codegen/c.zig | 36 ++-- src/codegen/wasm.zig | 6 +- src/link/Elf.zig | 2 +- src/llvm_backend.zig | 8 +- src/type.zig | 43 +--- src/value.zig | 599 +++++++++++++++++++++++++++++++-------------------- src/zir.zig | 34 +-- src/zir_sema.zig | 78 +++---- 12 files changed, 573 insertions(+), 515 deletions(-) (limited to 'src/codegen') diff --git a/src/Compilation.zig b/src/Compilation.zig index 11c8303fac..cd3db84ec2 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1457,11 +1457,12 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .complete, .codegen_failure_retryable => { const module = self.bin_file.options.module.?; - if (decl.typed_value.most_recent.typed_value.val.cast(Value.Payload.Function)) |payload| { - switch (payload.func.analysis) { - .queued => module.analyzeFnBody(decl, payload.func) catch |err| switch (err) { + if (decl.typed_value.most_recent.typed_value.val.castTag(.function)) |payload| { + const func = payload.data; + switch (func.analysis) { + .queued => module.analyzeFnBody(decl, func) catch |err| switch (err) { error.AnalysisFail => { - assert(payload.func.analysis != .in_progress); + assert(func.analysis != .in_progress); continue; }, error.OutOfMemory => return error.OutOfMemory, @@ -1475,7 +1476,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor var decl_arena = decl.typed_value.most_recent.arena.?.promote(module.gpa); defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; log.debug("analyze liveness of {}\n", .{decl.name}); - try liveness.analyze(module.gpa, &decl_arena.allocator, payload.func.analysis.success); + try liveness.analyze(module.gpa, &decl_arena.allocator, func.analysis.success); } assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); diff --git a/src/Module.zig b/src/Module.zig index 3e937fe49b..ca0718c3d5 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1092,16 +1092,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { tvm.deinit(self.gpa); } - const value_payload = try decl_arena.allocator.create(Value.Payload.ExternFn); - value_payload.* = .{ .decl = decl }; + const fn_val = try Value.Tag.extern_fn.create(&decl_arena.allocator, decl); decl_arena_state.* = decl_arena.state; decl.typed_value = .{ .most_recent = .{ - .typed_value = .{ - .ty = fn_type, - .val = Value.initPayload(&value_payload.base), - }, + .typed_value = .{ .ty = fn_type, .val = fn_val }, .arena = decl_arena_state, }, }; @@ -1187,7 +1183,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .analysis = .{ .queued = fn_zir }, .owner_decl = decl, }; - fn_payload.* = .{ .func = new_func }; + fn_payload.* = .{ + .base = .{ .tag = .function }, + .data = new_func, + }; var prev_type_has_bits = false; var type_changed = true; @@ -1375,7 +1374,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { } const new_variable = try decl_arena.allocator.create(Var); - const var_payload = try decl_arena.allocator.create(Value.Payload.Variable); new_variable.* = .{ .owner_decl = decl, .init = var_info.val orelse undefined, @@ -1383,14 +1381,14 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .is_mutable = is_mutable, .is_threadlocal = is_threadlocal, }; - var_payload.* = .{ .variable = new_variable }; + const var_val = try Value.Tag.variable.create(&decl_arena.allocator, new_variable); decl_arena_state.* = decl_arena.state; decl.typed_value = .{ .most_recent = .{ .typed_value = .{ .ty = var_info.ty, - .val = Value.initPayload(&var_payload.base), + .val = var_val, }, .arena = decl_arena_state, }, @@ -2232,52 +2230,43 @@ pub fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst { } pub fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst { - const int_payload = try scope.arena().create(Value.Payload.Int_u64); - int_payload.* = .{ .int = int }; - return self.constInst(scope, src, .{ .ty = ty, - .val = Value.initPayload(&int_payload.base), + .val = try Value.Tag.int_u64.create(scope.arena(), int), }); } pub fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst { - const int_payload = try scope.arena().create(Value.Payload.Int_i64); - int_payload.* = .{ .int = int }; - return self.constInst(scope, src, .{ .ty = ty, - .val = Value.initPayload(&int_payload.base), + .val = try Value.Tag.int_i64.create(scope.arena(), int), }); } pub fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst { - const val_payload = if (big_int.positive) blk: { + if (big_int.positive) { if (big_int.to(u64)) |x| { return self.constIntUnsigned(scope, src, ty, x); } else |err| switch (err) { error.NegativeIntoUnsigned => unreachable, error.TargetTooSmall => {}, // handled below } - const big_int_payload = try scope.arena().create(Value.Payload.IntBigPositive); - big_int_payload.* = .{ .limbs = big_int.limbs }; - break :blk &big_int_payload.base; - } else blk: { + return self.constInst(scope, src, .{ + .ty = ty, + .val = try Value.Tag.int_big_positive.create(scope.arena(), big_int.limbs), + }); + } else { if (big_int.to(i64)) |x| { return self.constIntSigned(scope, src, ty, x); } else |err| switch (err) { error.NegativeIntoUnsigned => unreachable, error.TargetTooSmall => {}, // handled below } - const big_int_payload = try scope.arena().create(Value.Payload.IntBigNegative); - big_int_payload.* = .{ .limbs = big_int.limbs }; - break :blk &big_int_payload.base; - }; - - return self.constInst(scope, src, .{ - .ty = ty, - .val = Value.initPayload(val_payload), - }); + return self.constInst(scope, src, .{ + .ty = ty, + .val = try Value.Tag.int_big_negative.create(scope.arena(), big_int.limbs), + }); + } } pub fn createAnonymousDecl( @@ -2346,26 +2335,20 @@ pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) Inn if (decl_tv.val.tag() == .variable) { return self.analyzeVarRef(scope, src, decl_tv); } - const ty = try self.simplePtrType(scope, src, decl_tv.ty, false, .One); - const val_payload = try scope.arena().create(Value.Payload.DeclRef); - val_payload.* = .{ .decl = decl }; - return self.constInst(scope, src, .{ - .ty = ty, - .val = Value.initPayload(&val_payload.base), + .ty = try self.simplePtrType(scope, src, decl_tv.ty, false, .One), + .val = try Value.Tag.decl_ref.create(scope.arena(), decl), }); } fn analyzeVarRef(self: *Module, scope: *Scope, src: usize, tv: TypedValue) InnerError!*Inst { - const variable = tv.val.cast(Value.Payload.Variable).?.variable; + const variable = tv.val.castTag(.variable).?.data; const ty = try self.simplePtrType(scope, src, tv.ty, variable.is_mutable, .One); if (!variable.is_mutable and !variable.is_extern) { - const val_payload = try scope.arena().create(Value.Payload.RefVal); - val_payload.* = .{ .val = variable.init }; return self.constInst(scope, src, .{ .ty = ty, - .val = Value.initPayload(&val_payload.base), + .val = try Value.Tag.ref_val.create(scope.arena(), variable.init), }); } @@ -3107,17 +3090,11 @@ pub fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { result_bigint.add(lhs_bigint, rhs_bigint); const result_limbs = result_bigint.limbs[0..result_bigint.len]; - const val_payload = if (result_bigint.positive) blk: { - const val_payload = try allocator.create(Value.Payload.IntBigPositive); - val_payload.* = .{ .limbs = result_limbs }; - break :blk &val_payload.base; - } else blk: { - const val_payload = try allocator.create(Value.Payload.IntBigNegative); - val_payload.* = .{ .limbs = result_limbs }; - break :blk &val_payload.base; - }; - - return Value.initPayload(val_payload); + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } } pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { @@ -3135,85 +3112,81 @@ pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { result_bigint.sub(lhs_bigint, rhs_bigint); const result_limbs = result_bigint.limbs[0..result_bigint.len]; - const val_payload = if (result_bigint.positive) blk: { - const val_payload = try allocator.create(Value.Payload.IntBigPositive); - val_payload.* = .{ .limbs = result_limbs }; - break :blk &val_payload.base; - } else blk: { - const val_payload = try allocator.create(Value.Payload.IntBigNegative); - val_payload.* = .{ .limbs = result_limbs }; - break :blk &val_payload.base; - }; - - return Value.initPayload(val_payload); + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } } -pub fn floatAdd(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value { - var bit_count = switch (float_type.tag()) { - .comptime_float => 128, - else => float_type.floatBits(self.getTarget()), - }; - - const allocator = scope.arena(); - const val_payload = switch (bit_count) { - 16 => { - return self.fail(scope, src, "TODO Implement addition for soft floats", .{}); +pub fn floatAdd( + self: *Module, + scope: *Scope, + float_type: Type, + src: usize, + lhs: Value, + rhs: Value, +) !Value { + const arena = scope.arena(); + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val + rhs_val); }, - 32 => blk: { + .f32 => { const lhs_val = lhs.toFloat(f32); const rhs_val = rhs.toFloat(f32); - const val_payload = try allocator.create(Value.Payload.Float_32); - val_payload.* = .{ .val = lhs_val + rhs_val }; - break :blk &val_payload.base; + return Value.Tag.float_32.create(arena, lhs_val + rhs_val); }, - 64 => blk: { + .f64 => { const lhs_val = lhs.toFloat(f64); const rhs_val = rhs.toFloat(f64); - const val_payload = try allocator.create(Value.Payload.Float_64); - val_payload.* = .{ .val = lhs_val + rhs_val }; - break :blk &val_payload.base; + return Value.Tag.float_64.create(arena, lhs_val + rhs_val); }, - 128 => { - return self.fail(scope, src, "TODO Implement addition for big floats", .{}); + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val + rhs_val); }, else => unreachable, - }; - - return Value.initPayload(val_payload); + } } -pub fn floatSub(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value { - var bit_count = switch (float_type.tag()) { - .comptime_float => 128, - else => float_type.floatBits(self.getTarget()), - }; - - const allocator = scope.arena(); - const val_payload = switch (bit_count) { - 16 => { - return self.fail(scope, src, "TODO Implement substraction for soft floats", .{}); +pub fn floatSub( + self: *Module, + scope: *Scope, + float_type: Type, + src: usize, + lhs: Value, + rhs: Value, +) !Value { + const arena = scope.arena(); + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val - rhs_val); }, - 32 => blk: { + .f32 => { const lhs_val = lhs.toFloat(f32); const rhs_val = rhs.toFloat(f32); - const val_payload = try allocator.create(Value.Payload.Float_32); - val_payload.* = .{ .val = lhs_val - rhs_val }; - break :blk &val_payload.base; + return Value.Tag.float_32.create(arena, lhs_val - rhs_val); }, - 64 => blk: { + .f64 => { const lhs_val = lhs.toFloat(f64); const rhs_val = rhs.toFloat(f64); - const val_payload = try allocator.create(Value.Payload.Float_64); - val_payload.* = .{ .val = lhs_val - rhs_val }; - break :blk &val_payload.base; + return Value.Tag.float_64.create(arena, lhs_val - rhs_val); }, - 128 => { - return self.fail(scope, src, "TODO Implement substraction for big floats", .{}); + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val - rhs_val); }, else => unreachable, - }; - - return Value.initPayload(val_payload); + } } pub fn simplePtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type, mutable: bool, size: std.builtin.TypeInfo.Pointer.Size) Allocator.Error!Type { diff --git a/src/astgen.zig b/src/astgen.zig index 1fc8a0d19e..c5261c4073 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1956,13 +1956,13 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo 32 => if (is_signed) Value.initTag(.i32_type) else Value.initTag(.u32_type), 64 => if (is_signed) Value.initTag(.i64_type) else Value.initTag(.u64_type), else => { - const int_type_payload = try scope.arena().create(Value.Payload.IntType); - int_type_payload.* = .{ .signed = is_signed, .bits = bit_count }; - const result = try addZIRInstConst(mod, scope, src, .{ + return rlWrap(mod, scope, rl, try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), - .val = Value.initPayload(&int_type_payload.base), - }); - return rlWrap(mod, scope, rl, result); + .val = try Value.Tag.int_type.create(scope.arena(), .{ + .signed = is_signed, + .bits = bit_count, + }), + })); }, }; const result = try addZIRInstConst(mod, scope, src, .{ @@ -2062,11 +2062,9 @@ fn charLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) !*zir.Inst }, }; - const int_payload = try scope.arena().create(Value.Payload.Int_u64); - int_payload.* = .{ .int = value }; return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.comptime_int), - .val = Value.initPayload(&int_payload.base), + .val = try Value.Tag.int_u64.create(scope.arena(), value), }); } @@ -2089,12 +2087,10 @@ fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) Inne prefixed_bytes[2..]; if (std.fmt.parseInt(u64, bytes, base)) |small_int| { - const int_payload = try arena.create(Value.Payload.Int_u64); - int_payload.* = .{ .int = small_int }; const src = tree.token_locs[int_lit.token].start; return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.comptime_int), - .val = Value.initPayload(&int_payload.base), + .val = try Value.Tag.int_u64.create(arena, small_int), }); } else |err| { return mod.failTok(scope, int_lit.token, "TODO implement int literals that don't fit in a u64", .{}); @@ -2109,15 +2105,13 @@ fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.OneToken) Inne return mod.failTok(scope, float_lit.token, "TODO hex floats", .{}); } - const val = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) { + const float_number = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) { error.InvalidCharacter => unreachable, // validated by tokenizer }; - const float_payload = try arena.create(Value.Payload.Float_128); - float_payload.* = .{ .val = val }; const src = tree.token_locs[float_lit.token].start; return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.comptime_float), - .val = Value.initPayload(&float_payload.base), + .val = try Value.Tag.float_128.create(arena, float_number), }); } diff --git a/src/codegen.zig b/src/codegen.zig index d98a87a440..f978115ebc 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -137,7 +137,7 @@ pub fn generateSymbol( }, .Array => { // TODO populate .debug_info for the array - if (typed_value.val.cast(Value.Payload.Bytes)) |payload| { + if (typed_value.val.castTag(.bytes)) |payload| { if (typed_value.ty.sentinel()) |sentinel| { try code.ensureCapacity(code.items.len + payload.data.len + 1); code.appendSliceAssumeCapacity(payload.data); @@ -168,8 +168,8 @@ pub fn generateSymbol( }, .Pointer => { // TODO populate .debug_info for the pointer - if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| { - const decl = payload.decl; + if (typed_value.val.castTag(.decl_ref)) |payload| { + const decl = payload.data; if (decl.analysis != .complete) return error.AnalysisFail; // TODO handle the dependency of this symbol on the decl's vaddr. // If the decl changes vaddr, then this symbol needs to get regenerated. @@ -432,7 +432,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { @panic("Attempted to compile for architecture that was disabled by build configuration"); } - const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; + const module_fn = typed_value.val.castTag(.function).?.data; const fn_type = module_fn.owner_decl.typed_value.most_recent.typed_value.ty; @@ -1579,9 +1579,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - if (inst.func.cast(ir.Inst.Constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const func = func_val.func; + if (inst.func.value()) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); @@ -1607,9 +1607,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .riscv64 => { if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch}); - if (inst.func.cast(ir.Inst.Constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const func = func_val.func; + if (inst.func.value()) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); @@ -1631,12 +1631,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } }, .spu_2 => { - if (inst.func.cast(ir.Inst.Constant)) |func_inst| { + if (inst.func.value()) |func_value| { if (info.args.len != 0) { return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{}); } - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const func = func_val.func; + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; break :blk @intCast(u16, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * 2); @@ -1705,9 +1705,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - if (inst.func.cast(ir.Inst.Constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const func = func_val.func; + if (inst.func.value()) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { @@ -1766,9 +1766,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - if (inst.func.cast(ir.Inst.Constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const func = func_val.func; + if (inst.func.value()) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: { @@ -1825,9 +1825,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - if (inst.func.cast(ir.Inst.Constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const func = func_val.func; + if (inst.func.value()) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; const text_segment = &macho_file.load_commands.items[macho_file.text_segment_cmd_index.?].Segment; const got = &text_segment.sections.items[macho_file.got_section_index.?]; const got_addr = got.addr + func.owner_decl.link.macho.offset_table_index * @sizeOf(u64); @@ -3223,20 +3223,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const ptr_bytes: u64 = @divExact(ptr_bits, 8); switch (typed_value.ty.zigTypeTag()) { .Pointer => { - if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| { + if (typed_value.val.castTag(.decl_ref)) |payload| { if (self.bin_file.cast(link.File.Elf)) |elf_file| { - const decl = payload.decl; + const decl = payload.data; const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { - const decl = payload.decl; + const decl = payload.data; const text_segment = &macho_file.load_commands.items[macho_file.text_segment_cmd_index.?].Segment; const got = &text_segment.sections.items[macho_file.got_section_index.?]; const got_addr = got.addr + decl.link.macho.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const decl = payload.decl; + const decl = payload.data; const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 828061fac6..7cd4479bd9 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -138,25 +138,25 @@ fn renderValue( .undef, .zero => try writer.writeAll("0"), .one => try writer.writeAll("1"), .decl_ref => { - const decl_ref_payload = val.cast(Value.Payload.DeclRef).?; + const decl = val.castTag(.decl_ref).?.data; // Determine if we must pointer cast. - const decl_tv = decl_ref_payload.decl.typed_value.most_recent.typed_value; + const decl_tv = decl.typed_value.most_recent.typed_value; if (t.eql(decl_tv.ty)) { - try writer.print("&{s}", .{decl_ref_payload.decl.name}); + try writer.print("&{s}", .{decl.name}); } else { try writer.writeAll("("); try renderType(ctx, writer, t); - try writer.print(")&{s}", .{decl_ref_payload.decl.name}); + try writer.print(")&{s}", .{decl.name}); } }, .function => { - const payload = val.cast(Value.Payload.Function).?; - try writer.print("{s}", .{payload.func.owner_decl.name}); + const func = val.castTag(.function).?.data; + try writer.print("{s}", .{func.owner_decl.name}); }, .extern_fn => { - const payload = val.cast(Value.Payload.ExternFn).?; - try writer.print("{s}", .{payload.decl.name}); + const decl = val.castTag(.extern_fn).?.data; + try writer.print("{s}", .{decl.name}); }, else => |e| return ctx.fail( ctx.decl.src(), @@ -169,7 +169,7 @@ fn renderValue( switch (val.tag()) { .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), .bytes => { - const bytes = val.cast(Value.Payload.Bytes).?.data; + const bytes = val.castTag(.bytes).?.data; // TODO: make our own C string escape instead of using {Z} try writer.print("\"{Z}\"", .{bytes}); }, @@ -209,7 +209,7 @@ fn renderFunctionSignature( switch (tv.val.tag()) { .extern_fn => break :blk true, .function => { - const func = tv.val.cast(Value.Payload.Function).?.func; + const func = tv.val.castTag(.function).?.data; break :blk ctx.module.decl_exports.contains(func.owner_decl); }, else => unreachable, @@ -268,13 +268,13 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { ctx.deinit(); } - if (tv.val.cast(Value.Payload.Function)) |func_payload| { + if (tv.val.castTag(.function)) |func_payload| { const writer = file.main.writer(); try renderFunctionSignature(&ctx, writer, decl); try writer.writeAll(" {"); - const func: *Module.Fn = func_payload.func; + const func: *Module.Fn = func_payload.data; const instructions = func.analysis.success.instructions; if (instructions.len > 0) { try writer.writeAll("\n"); @@ -480,10 +480,10 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { const writer = file.main.writer(); const header = file.header.buf.writer(); if (inst.func.castTag(.constant)) |func_inst| { - const fn_decl = if (func_inst.val.cast(Value.Payload.ExternFn)) |extern_fn| - extern_fn.decl - else if (func_inst.val.cast(Value.Payload.Function)) |func_val| - func_val.func.owner_decl + const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn| + extern_fn.data + else if (func_inst.val.castTag(.function)) |func_payload| + func_payload.data.owner_decl else unreachable; @@ -513,8 +513,8 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { if (i > 0) { try writer.writeAll(", "); } - if (arg.cast(Inst.Constant)) |con| { - try renderValue(ctx, writer, arg.ty, con.val); + if (arg.value()) |val| { + try renderValue(ctx, writer, arg.ty, val); } else { const val = try ctx.resolveInst(arg); try writer.print("{}", .{val}); diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 7297ea1d54..c7ad59f5d1 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -62,7 +62,7 @@ pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void { // Write instructions // TODO: check for and handle death of instructions const tv = decl.typed_value.most_recent.typed_value; - const mod_fn = tv.val.cast(Value.Payload.Function).?.func; + const mod_fn = tv.val.castTag(.function).?.data; for (mod_fn.analysis.success.instructions) |inst| try genInst(buf, decl, inst); // Write 'end' opcode @@ -125,8 +125,8 @@ fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void { fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void { const func_inst = inst.func.castTag(.constant).?; - const func_val = func_inst.val.cast(Value.Payload.Function).?; - const target = func_val.func.owner_decl; + const func = func_inst.val.castTag(.function).?.data; + const target = func.owner_decl; const target_ty = target.typed_value.most_recent.typed_value.ty; if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 25b883f8c6..4b2b95fc72 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2183,7 +2183,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { for (zir_dumps) |fn_name| { if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) { std.debug.print("\n{}\n", .{decl.name}); - typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*); + typed_value.val.castTag(.function).?.data.dump(module.*); } } } diff --git a/src/llvm_backend.zig b/src/llvm_backend.zig index 77aa4d3bd5..294e6f5400 100644 --- a/src/llvm_backend.zig +++ b/src/llvm_backend.zig @@ -280,7 +280,7 @@ pub const LLVMIRModule = struct { fn gen(self: *LLVMIRModule, module: *Module, typed_value: TypedValue, src: usize) !void { switch (typed_value.ty.zigTypeTag()) { .Fn => { - const func = typed_value.val.cast(Value.Payload.Function).?.func; + const func = typed_value.val.castTag(.function).?.data; const llvm_func = try self.resolveLLVMFunction(func); @@ -314,9 +314,9 @@ pub const LLVMIRModule = struct { } fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !void { - if (inst.func.cast(Inst.Constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const func = func_val.func; + if (inst.func.value()) |func_value| { + if (func_value.castTag(.function)) |func_payload| { + const func = func_payload.data; const zig_fn_type = func.owner_decl.typed_value.most_recent.typed_value.ty; const llvm_fn = try self.resolveLLVMFunction(func); diff --git a/src/type.zig b/src/type.zig index ce237f89c7..9d834a19f2 100644 --- a/src/type.zig +++ b/src/type.zig @@ -733,11 +733,7 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type), .const_slice_u8 => return Value.initTag(.const_slice_u8_type), .enum_literal => return Value.initTag(.enum_literal_type), - else => { - const ty_payload = try allocator.create(Value.Payload.Ty); - ty_payload.* = .{ .ty = self }; - return Value.initPayload(&ty_payload.base); - }, + else => return Value.Tag.ty.create(allocator, self), } } @@ -2951,11 +2947,8 @@ pub const Type = extern union { } if ((info.bits - 1) <= std.math.maxInt(u6)) { - const payload = try arena.allocator.create(Value.Payload.Int_i64); - payload.* = .{ - .int = -(@as(i64, 1) << @truncate(u6, info.bits - 1)), - }; - return Value.initPayload(&payload.base); + const n: i64 = -(@as(i64, 1) << @truncate(u6, info.bits - 1)); + return Value.Tag.int_i64.create(&arena.allocator, n); } var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1); @@ -2964,13 +2957,9 @@ pub const Type = extern union { const res_const = res.toConst(); if (res_const.positive) { - const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive); - val_payload.* = .{ .limbs = res_const.limbs }; - return Value.initPayload(&val_payload.base); + return Value.Tag.int_big_positive.create(&arena.allocator, res_const.limbs); } else { - const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative); - val_payload.* = .{ .limbs = res_const.limbs }; - return Value.initPayload(&val_payload.base); + return Value.Tag.int_big_negative.create(&arena.allocator, res_const.limbs); } } @@ -2980,17 +2969,11 @@ pub const Type = extern union { const info = self.intInfo(target); if (info.signedness == .signed and (info.bits - 1) <= std.math.maxInt(u6)) { - const payload = try arena.allocator.create(Value.Payload.Int_i64); - payload.* = .{ - .int = (@as(i64, 1) << @truncate(u6, info.bits - 1)) - 1, - }; - return Value.initPayload(&payload.base); + const n: i64 = (@as(i64, 1) << @truncate(u6, info.bits - 1)) - 1; + return Value.Tag.int_i64.create(&arena.allocator, n); } else if (info.signedness == .signed and info.bits <= std.math.maxInt(u6)) { - const payload = try arena.allocator.create(Value.Payload.Int_u64); - payload.* = .{ - .int = (@as(u64, 1) << @truncate(u6, info.bits)) - 1, - }; - return Value.initPayload(&payload.base); + const n: u64 = (@as(u64, 1) << @truncate(u6, info.bits)) - 1; + return Value.Tag.int_u64.create(&arena.allocator, n); } var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1); @@ -3003,13 +2986,9 @@ pub const Type = extern union { const res_const = res.toConst(); if (res_const.positive) { - const val_payload = try arena.allocator.create(Value.Payload.IntBigPositive); - val_payload.* = .{ .limbs = res_const.limbs }; - return Value.initPayload(&val_payload.base); + return Value.Tag.int_big_positive.create(&arena.allocator, res_const.limbs); } else { - const val_payload = try arena.allocator.create(Value.Payload.IntBigNegative); - val_payload.* = .{ .limbs = res_const.limbs }; - return Value.initPayload(&val_payload.base); + return Value.Tag.int_big_negative.create(&arena.allocator, res_const.limbs); } } diff --git a/src/value.zig b/src/value.zig index f26c8d8772..91b21511d4 100644 --- a/src/value.zig +++ b/src/value.zig @@ -84,11 +84,16 @@ pub const Value = extern union { function, extern_fn, variable, + /// Represents a pointer to another immutable value. ref_val, + /// Represents a pointer to a decl, not the value of the decl. decl_ref, elem_ptr, + /// A slice of u8 whose memory is managed externally. bytes, - repeated, // the value is a value repeated some number of times + /// This value is repeated some number of times. The amount of times to repeat + /// is stored externally. + repeated, float_16, float_32, float_64, @@ -99,6 +104,106 @@ pub const Value = extern union { pub const last_no_payload_tag = Tag.bool_false; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; + + pub fn Type(comptime t: Tag) type { + return switch (t) { + .u8_type, + .i8_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, + .usize_type, + .isize_type, + .c_short_type, + .c_ushort_type, + .c_int_type, + .c_uint_type, + .c_long_type, + .c_ulong_type, + .c_longlong_type, + .c_ulonglong_type, + .c_longdouble_type, + .f16_type, + .f32_type, + .f64_type, + .f128_type, + .c_void_type, + .bool_type, + .void_type, + .type_type, + .anyerror_type, + .comptime_int_type, + .comptime_float_type, + .noreturn_type, + .null_type, + .undefined_type, + .fn_noreturn_no_args_type, + .fn_void_no_args_type, + .fn_naked_noreturn_no_args_type, + .fn_ccc_void_no_args_type, + .single_const_pointer_to_comptime_int_type, + .const_slice_u8_type, + .enum_literal_type, + .anyframe_type, + .undef, + .zero, + .one, + .void_value, + .unreachable_value, + .empty_struct_value, + .empty_array, + .null_value, + .bool_true, + .bool_false, + => @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"), + + .int_big_positive, + .int_big_negative, + => Payload.BigInt, + + .extern_fn, + .decl_ref, + => Payload.Decl, + + .ref_val, + .repeated, + => Payload.SubValue, + + .bytes, + .enum_literal, + => Payload.Bytes, + + .ty => Payload.Ty, + .int_type => Payload.IntType, + .int_u64 => Payload.U64, + .int_i64 => Payload.I64, + .function => Payload.Function, + .variable => Payload.Variable, + .elem_ptr => Payload.ElemPtr, + .float_16 => Payload.Float_16, + .float_32 => Payload.Float_32, + .float_64 => Payload.Float_64, + .float_128 => Payload.Float_128, + .error_set => Payload.ErrorSet, + .@"error" => Payload.Error, + }; + } + + pub fn create(comptime t: Tag, ally: *Allocator, data: Data(t)) error{OutOfMemory}!Value { + const ptr = try ally.create(t.Type()); + ptr.* = .{ + .base = .{ .tag = t }, + .data = data, + }; + return Value{ .ptr_otherwise = &ptr.base }; + } + + pub fn Data(comptime t: Tag) type { + return std.meta.fieldInfo(t.Type(), "data").field_type; + } }; pub fn initTag(small_tag: Tag) Value { @@ -119,15 +224,36 @@ pub const Value = extern union { } } + /// Prefer `castTag` to this. pub fn cast(self: Value, comptime T: type) ?*T { - if (self.tag_if_small_enough < Tag.no_payload_count) + if (@hasField(T, "base_tag")) { + return base.castTag(T.base_tag); + } + if (self.tag_if_small_enough < Tag.no_payload_count) { return null; + } + inline for (@typeInfo(Tag).Enum.fields) |field| { + if (field.value < Tag.no_payload_count) + continue; + const t = @intToEnum(Tag, field.value); + if (self.ptr_otherwise.tag == t) { + if (T == t.Type()) { + return @fieldParentPtr(T, "base", self.ptr_otherwise); + } + return null; + } + } + unreachable; + } - const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag; - if (self.ptr_otherwise.tag != expected_tag) + pub fn castTag(self: Value, comptime t: Tag) ?*t.Type() { + if (self.tag_if_small_enough < Tag.no_payload_count) return null; - return @fieldParentPtr(T, "base", self.ptr_otherwise); + if (self.ptr_otherwise.tag == t) + return @fieldParentPtr(t.Type(), "base", self.ptr_otherwise); + + return null; } pub fn copy(self: Value, allocator: *Allocator) error{OutOfMemory}!Value { @@ -188,17 +314,17 @@ pub const Value = extern union { => unreachable, .ty => { - const payload = @fieldParentPtr(Payload.Ty, "base", self.ptr_otherwise); + const payload = self.castTag(.ty).?; const new_payload = try allocator.create(Payload.Ty); new_payload.* = .{ .base = payload.base, - .ty = try payload.ty.copy(allocator), + .data = try payload.data.copy(allocator), }; return Value{ .ptr_otherwise = &new_payload.base }; }, .int_type => return self.copyPayloadShallow(allocator, Payload.IntType), - .int_u64 => return self.copyPayloadShallow(allocator, Payload.Int_u64), - .int_i64 => return self.copyPayloadShallow(allocator, Payload.Int_i64), + .int_u64 => return self.copyPayloadShallow(allocator, Payload.U64), + .int_i64 => return self.copyPayloadShallow(allocator, Payload.I64), .int_big_positive => { @panic("TODO implement copying of big ints"); }, @@ -206,35 +332,37 @@ pub const Value = extern union { @panic("TODO implement copying of big ints"); }, .function => return self.copyPayloadShallow(allocator, Payload.Function), - .extern_fn => return self.copyPayloadShallow(allocator, Payload.ExternFn), + .extern_fn => return self.copyPayloadShallow(allocator, Payload.Decl), .variable => return self.copyPayloadShallow(allocator, Payload.Variable), .ref_val => { - const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise); - const new_payload = try allocator.create(Payload.RefVal); + const payload = self.castTag(.ref_val).?; + const new_payload = try allocator.create(Payload.SubValue); new_payload.* = .{ .base = payload.base, - .val = try payload.val.copy(allocator), + .data = try payload.data.copy(allocator), }; return Value{ .ptr_otherwise = &new_payload.base }; }, - .decl_ref => return self.copyPayloadShallow(allocator, Payload.DeclRef), + .decl_ref => return self.copyPayloadShallow(allocator, Payload.Decl), .elem_ptr => { - const payload = @fieldParentPtr(Payload.ElemPtr, "base", self.ptr_otherwise); + const payload = self.castTag(.elem_ptr).?; const new_payload = try allocator.create(Payload.ElemPtr); new_payload.* = .{ .base = payload.base, - .array_ptr = try payload.array_ptr.copy(allocator), - .index = payload.index, + .data = .{ + .array_ptr = try payload.data.array_ptr.copy(allocator), + .index = payload.data.index, + }, }; return Value{ .ptr_otherwise = &new_payload.base }; }, .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), .repeated => { - const payload = @fieldParentPtr(Payload.Repeated, "base", self.ptr_otherwise); - const new_payload = try allocator.create(Payload.Repeated); + const payload = self.castTag(.repeated).?; + const new_payload = try allocator.create(Payload.SubValue); new_payload.* = .{ .base = payload.base, - .val = try payload.val.copy(allocator), + .data = try payload.data.copy(allocator), }; return Value{ .ptr_otherwise = &new_payload.base }; }, @@ -243,7 +371,7 @@ pub const Value = extern union { .float_64 => return self.copyPayloadShallow(allocator, Payload.Float_64), .float_128 => return self.copyPayloadShallow(allocator, Payload.Float_128), .enum_literal => { - const payload = @fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise); + const payload = self.castTag(.enum_literal).?; const new_payload = try allocator.create(Payload.Bytes); new_payload.* = .{ .base = payload.base, @@ -259,7 +387,7 @@ pub const Value = extern union { } fn copyPayloadShallow(self: Value, allocator: *Allocator, comptime T: type) error{OutOfMemory}!Value { - const payload = @fieldParentPtr(T, "base", self.ptr_otherwise); + const payload = self.cast(T).?; const new_payload = try allocator.create(T); new_payload.* = payload.*; return Value{ .ptr_otherwise = &new_payload.base }; @@ -326,45 +454,45 @@ pub const Value = extern union { .unreachable_value => return out_stream.writeAll("unreachable"), .bool_true => return out_stream.writeAll("true"), .bool_false => return out_stream.writeAll("false"), - .ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream), + .ty => return val.castTag(.ty).?.data.format("", options, out_stream), .int_type => { - const int_type = val.cast(Payload.IntType).?; + const int_type = val.castTag(.int_type).?.data; return out_stream.print("{}{}", .{ if (int_type.signed) "s" else "u", int_type.bits, }); }, - .int_u64 => return std.fmt.formatIntValue(val.cast(Payload.Int_u64).?.int, "", options, out_stream), - .int_i64 => return std.fmt.formatIntValue(val.cast(Payload.Int_i64).?.int, "", options, out_stream), - .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}), - .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}), + .int_u64 => return std.fmt.formatIntValue(val.castTag(.int_u64).?.data, "", options, out_stream), + .int_i64 => return std.fmt.formatIntValue(val.castTag(.int_i64).?.data, "", options, out_stream), + .int_big_positive => return out_stream.print("{}", .{val.castTag(.int_big_positive).?.asBigInt()}), + .int_big_negative => return out_stream.print("{}", .{val.castTag(.int_big_negative).?.asBigInt()}), .function => return out_stream.writeAll("(function)"), .extern_fn => return out_stream.writeAll("(extern function)"), .variable => return out_stream.writeAll("(variable)"), .ref_val => { - const ref_val = val.cast(Payload.RefVal).?; + const ref_val = val.castTag(.ref_val).?.data; try out_stream.writeAll("&const "); - val = ref_val.val; + val = ref_val; }, .decl_ref => return out_stream.writeAll("(decl ref)"), .elem_ptr => { - const elem_ptr = val.cast(Payload.ElemPtr).?; + const elem_ptr = val.castTag(.elem_ptr).?.data; try out_stream.print("&[{}] ", .{elem_ptr.index}); val = elem_ptr.array_ptr; }, .empty_array => return out_stream.writeAll(".{}"), - .enum_literal => return out_stream.print(".{z}", .{self.cast(Payload.Bytes).?.data}), - .bytes => return out_stream.print("\"{Z}\"", .{self.cast(Payload.Bytes).?.data}), + .enum_literal => return out_stream.print(".{z}", .{self.castTag(.enum_literal).?.data}), + .bytes => return out_stream.print("\"{Z}\"", .{self.castTag(.bytes).?.data}), .repeated => { try out_stream.writeAll("(repeated) "); - val = val.cast(Payload.Repeated).?.val; + val = val.castTag(.repeated).?.data; }, - .float_16 => return out_stream.print("{}", .{val.cast(Payload.Float_16).?.val}), - .float_32 => return out_stream.print("{}", .{val.cast(Payload.Float_32).?.val}), - .float_64 => return out_stream.print("{}", .{val.cast(Payload.Float_64).?.val}), - .float_128 => return out_stream.print("{}", .{val.cast(Payload.Float_128).?.val}), + .float_16 => return out_stream.print("{}", .{val.castTag(.float_16).?.data}), + .float_32 => return out_stream.print("{}", .{val.castTag(.float_32).?.data}), + .float_64 => return out_stream.print("{}", .{val.castTag(.float_64).?.data}), + .float_128 => return out_stream.print("{}", .{val.castTag(.float_128).?.data}), .error_set => { - const error_set = val.cast(Payload.ErrorSet).?; + const error_set = val.castTag(.error_set).?.data; try out_stream.writeAll("error{"); var it = error_set.fields.iterator(); while (it.next()) |entry| { @@ -372,21 +500,24 @@ pub const Value = extern union { } return out_stream.writeAll("}"); }, - .@"error" => return out_stream.print("error.{}", .{val.cast(Payload.Error).?.name}), + .@"error" => return out_stream.print("error.{}", .{val.castTag(.@"error").?.data.name}), }; } /// Asserts that the value is representable as an array of bytes. /// Copies the value into a freshly allocated slice of memory, which is owned by the caller. pub fn toAllocatedBytes(self: Value, allocator: *Allocator) ![]u8 { - if (self.cast(Payload.Bytes)) |bytes| { - return std.mem.dupe(allocator, u8, bytes.data); + if (self.castTag(.bytes)) |payload| { + return std.mem.dupe(allocator, u8, payload.data); } - if (self.cast(Payload.Repeated)) |repeated| { + if (self.castTag(.enum_literal)) |payload| { + return std.mem.dupe(allocator, u8, payload.data); + } + if (self.castTag(.repeated)) |payload| { @panic("TODO implement toAllocatedBytes for this Value tag"); } - if (self.cast(Payload.DeclRef)) |declref| { - const val = try declref.decl.value(); + if (self.castTag(.decl_ref)) |payload| { + const val = try payload.data.value(); return val.toAllocatedBytes(allocator); } unreachable; @@ -395,7 +526,7 @@ pub const Value = extern union { /// Asserts that the value is representable as a type. pub fn toType(self: Value, allocator: *Allocator) !Type { return switch (self.tag()) { - .ty => self.cast(Payload.Ty).?.ty, + .ty => self.castTag(.ty).?.data, .u8_type => Type.initTag(.u8), .i8_type => Type.initTag(.i8), .u16_type => Type.initTag(.u16), @@ -439,7 +570,7 @@ pub const Value = extern union { .anyframe_type => Type.initTag(.@"anyframe"), .int_type => { - const payload = self.cast(Payload.IntType).?; + const payload = self.castTag(.int_type).?.data; const new = try allocator.create(Type.Payload.Bits); new.* = .{ .base = .{ @@ -450,7 +581,7 @@ pub const Value = extern union { return Type.initPayload(&new.base); }, .error_set => { - const payload = self.cast(Payload.ErrorSet).?; + const payload = self.castTag(.error_set).?.data; return Type.Tag.error_set.create(allocator, payload.decl); }, @@ -564,10 +695,10 @@ pub const Value = extern union { .bool_true, => return BigIntMutable.init(&space.limbs, 1).toConst(), - .int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(), - .int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(), - .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(), - .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt(), + .int_u64 => return BigIntMutable.init(&space.limbs, self.castTag(.int_u64).?.data).toConst(), + .int_i64 => return BigIntMutable.init(&space.limbs, self.castTag(.int_i64).?.data).toConst(), + .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt(), + .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt(), } } @@ -649,10 +780,10 @@ pub const Value = extern union { .bool_true, => return 1, - .int_u64 => return self.cast(Payload.Int_u64).?.int, - .int_i64 => return @intCast(u64, self.cast(Payload.Int_i64).?.int), - .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(u64) catch unreachable, - .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().to(u64) catch unreachable, + .int_u64 => return self.castTag(.int_u64).?.data, + .int_i64 => return @intCast(u64, self.castTag(.int_i64).?.data), + .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().to(u64) catch unreachable, + .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().to(u64) catch unreachable, } } @@ -734,10 +865,10 @@ pub const Value = extern union { .bool_true, => return 1, - .int_u64 => return @intCast(i64, self.cast(Payload.Int_u64).?.int), - .int_i64 => return self.cast(Payload.Int_i64).?.int, - .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(i64) catch unreachable, - .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().to(i64) catch unreachable, + .int_u64 => return @intCast(i64, self.castTag(.int_u64).?.data), + .int_i64 => return self.castTag(.int_i64).?.data, + .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().to(i64) catch unreachable, + .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().to(i64) catch unreachable, } } @@ -753,14 +884,14 @@ pub const Value = extern union { pub fn toFloat(self: Value, comptime T: type) T { return switch (self.tag()) { .float_16 => @panic("TODO soft float"), - .float_32 => @floatCast(T, self.cast(Payload.Float_32).?.val), - .float_64 => @floatCast(T, self.cast(Payload.Float_64).?.val), - .float_128 => @floatCast(T, self.cast(Payload.Float_128).?.val), + .float_32 => @floatCast(T, self.castTag(.float_32).?.data), + .float_64 => @floatCast(T, self.castTag(.float_64).?.data), + .float_128 => @floatCast(T, self.castTag(.float_128).?.data), .zero => 0, .one => 1, - .int_u64 => @intToFloat(T, self.cast(Payload.Int_u64).?.int), - .int_i64 => @intToFloat(T, self.cast(Payload.Int_i64).?.int), + .int_u64 => @intToFloat(T, self.castTag(.int_u64).?.data), + .int_i64 => @intToFloat(T, self.castTag(.int_i64).?.data), .int_big_positive, .int_big_negative => @panic("big int to f128"), else => unreachable, @@ -846,15 +977,15 @@ pub const Value = extern union { => return 1, .int_u64 => { - const x = self.cast(Payload.Int_u64).?.int; + const x = self.castTag(.int_u64).?.data; if (x == 0) return 0; return @intCast(usize, std.math.log2(x) + 1); }, .int_i64 => { @panic("TODO implement i64 intBitCountTwosComp"); }, - .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().bitCountTwosComp(), - .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().bitCountTwosComp(), + .int_big_positive => return self.castTag(.int_big_positive).?.asBigInt().bitCountTwosComp(), + .int_big_negative => return self.castTag(.int_big_negative).?.asBigInt().bitCountTwosComp(), } } @@ -943,7 +1074,7 @@ pub const Value = extern union { .int_u64 => switch (ty.zigTypeTag()) { .Int => { - const x = self.cast(Payload.Int_u64).?.int; + const x = self.castTag(.int_u64).?.data; if (x == 0) return true; const info = ty.intInfo(target); const needed_bits = std.math.log2(x) + 1 + @boolToInt(info.signedness == .signed); @@ -954,7 +1085,7 @@ pub const Value = extern union { }, .int_i64 => switch (ty.zigTypeTag()) { .Int => { - const x = self.cast(Payload.Int_i64).?.int; + const x = self.castTag(.int_i64).?.data; if (x == 0) return true; const info = ty.intInfo(target); if (info.signedness == .unsigned and x < 0) @@ -967,7 +1098,7 @@ pub const Value = extern union { .int_big_positive => switch (ty.zigTypeTag()) { .Int => { const info = ty.intInfo(target); - return self.cast(Payload.IntBigPositive).?.asBigInt().fitsInTwosComp(info.signedness, info.bits); + return self.castTag(.int_big_positive).?.asBigInt().fitsInTwosComp(info.signedness, info.bits); }, .ComptimeInt => return true, else => unreachable, @@ -975,7 +1106,7 @@ pub const Value = extern union { .int_big_negative => switch (ty.zigTypeTag()) { .Int => { const info = ty.intInfo(target); - return self.cast(Payload.IntBigNegative).?.asBigInt().fitsInTwosComp(info.signedness, info.bits); + return self.castTag(.int_big_negative).?.asBigInt().fitsInTwosComp(info.signedness, info.bits); }, .ComptimeInt => return true, else => unreachable, @@ -986,42 +1117,28 @@ pub const Value = extern union { /// Converts an integer or a float to a float. /// Returns `error.Overflow` if the value does not fit in the new type. pub fn floatCast(self: Value, allocator: *Allocator, ty: Type, target: Target) !Value { - const dest_bit_count = switch (ty.tag()) { - .comptime_float => 128, - else => ty.floatBits(target), - }; - switch (dest_bit_count) { - 16, 32, 64, 128 => {}, - else => std.debug.panic("TODO float cast bit count {}\n", .{dest_bit_count}), - } - if (ty.isInt()) { - @panic("TODO int to float"); - } - - switch (dest_bit_count) { - 16 => { - @panic("TODO soft float"); - // var res_payload = Value.Payload.Float_16{.val = self.toFloat(f16)}; - // if (!self.eql(Value.initPayload(&res_payload.base))) - // return error.Overflow; - // return Value.initPayload(&res_payload.base).copy(allocator); + switch (ty.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const res = try Value.Tag.float_16.create(allocator, self.toFloat(f16)); + //if (!self.eql(res)) + // return error.Overflow; + //return res; }, - 32 => { - var res_payload = Value.Payload.Float_32{ .val = self.toFloat(f32) }; - if (!self.eql(Value.initPayload(&res_payload.base))) + .f32 => { + const res = try Value.Tag.float_32.create(allocator, self.toFloat(f32)); + if (!self.eql(res)) return error.Overflow; - return Value.initPayload(&res_payload.base).copy(allocator); + return res; }, - 64 => { - var res_payload = Value.Payload.Float_64{ .val = self.toFloat(f64) }; - if (!self.eql(Value.initPayload(&res_payload.base))) + .f64 => { + const res = try Value.Tag.float_64.create(allocator, self.toFloat(f64)); + if (!self.eql(res)) return error.Overflow; - return Value.initPayload(&res_payload.base).copy(allocator); + return res; }, - 128 => { - const float_payload = try allocator.create(Value.Payload.Float_128); - float_payload.* = .{ .val = self.toFloat(f128) }; - return Value.initPayload(&float_payload.base); + .f128, .comptime_float, .c_longdouble => { + return Value.Tag.float_128.create(allocator, self.toFloat(f128)); }, else => unreachable, } @@ -1102,10 +1219,10 @@ pub const Value = extern union { .one, => false, - .float_16 => @rem(self.cast(Payload.Float_16).?.val, 1) != 0, - .float_32 => @rem(self.cast(Payload.Float_32).?.val, 1) != 0, - .float_64 => @rem(self.cast(Payload.Float_64).?.val, 1) != 0, - // .float_128 => @rem(self.cast(Payload.Float_128).?.val, 1) != 0, + .float_16 => @rem(self.castTag(.float_16).?.data, 1) != 0, + .float_32 => @rem(self.castTag(.float_32).?.data, 1) != 0, + .float_64 => @rem(self.castTag(.float_64).?.data, 1) != 0, + // .float_128 => @rem(self.castTag(.float_128).?.data, 1) != 0, .float_128 => @panic("TODO lld: error: undefined symbol: fmodl"), }; } @@ -1182,15 +1299,15 @@ pub const Value = extern union { .bool_true, => .gt, - .int_u64 => std.math.order(lhs.cast(Payload.Int_u64).?.int, 0), - .int_i64 => std.math.order(lhs.cast(Payload.Int_i64).?.int, 0), - .int_big_positive => lhs.cast(Payload.IntBigPositive).?.asBigInt().orderAgainstScalar(0), - .int_big_negative => lhs.cast(Payload.IntBigNegative).?.asBigInt().orderAgainstScalar(0), + .int_u64 => std.math.order(lhs.castTag(.int_u64).?.data, 0), + .int_i64 => std.math.order(lhs.castTag(.int_i64).?.data, 0), + .int_big_positive => lhs.castTag(.int_big_positive).?.asBigInt().orderAgainstScalar(0), + .int_big_negative => lhs.castTag(.int_big_negative).?.asBigInt().orderAgainstScalar(0), - .float_16 => std.math.order(lhs.cast(Payload.Float_16).?.val, 0), - .float_32 => std.math.order(lhs.cast(Payload.Float_32).?.val, 0), - .float_64 => std.math.order(lhs.cast(Payload.Float_64).?.val, 0), - .float_128 => std.math.order(lhs.cast(Payload.Float_128).?.val, 0), + .float_16 => std.math.order(lhs.castTag(.float_16).?.data, 0), + .float_32 => std.math.order(lhs.castTag(.float_32).?.data, 0), + .float_64 => std.math.order(lhs.castTag(.float_64).?.data, 0), + .float_128 => std.math.order(lhs.castTag(.float_128).?.data, 0), }; } @@ -1208,10 +1325,10 @@ pub const Value = extern union { if (lhs_float and rhs_float) { if (lhs_tag == rhs_tag) { return switch (lhs.tag()) { - .float_16 => return std.math.order(lhs.cast(Payload.Float_16).?.val, rhs.cast(Payload.Float_16).?.val), - .float_32 => return std.math.order(lhs.cast(Payload.Float_32).?.val, rhs.cast(Payload.Float_32).?.val), - .float_64 => return std.math.order(lhs.cast(Payload.Float_64).?.val, rhs.cast(Payload.Float_64).?.val), - .float_128 => return std.math.order(lhs.cast(Payload.Float_128).?.val, rhs.cast(Payload.Float_128).?.val), + .float_16 => return std.math.order(lhs.castTag(.float_16).?.data, rhs.castTag(.float_16).?.data), + .float_32 => return std.math.order(lhs.castTag(.float_32).?.data, rhs.castTag(.float_32).?.data), + .float_64 => return std.math.order(lhs.castTag(.float_64).?.data, rhs.castTag(.float_64).?.data), + .float_128 => return std.math.order(lhs.castTag(.float_128).?.data, rhs.castTag(.float_128).?.data), else => unreachable, }; } @@ -1244,8 +1361,8 @@ pub const Value = extern union { if (a.tag() == .void_value or a.tag() == .null_value) { return true; } else if (a.tag() == .enum_literal) { - const a_name = @fieldParentPtr(Payload.Bytes, "base", a.ptr_otherwise).data; - const b_name = @fieldParentPtr(Payload.Bytes, "base", b.ptr_otherwise).data; + const a_name = a.castTag(.enum_literal).?.data; + const b_name = b.castTag(.enum_literal).?.data; return std.mem.eql(u8, a_name, b_name); } } @@ -1313,11 +1430,11 @@ pub const Value = extern union { }, .error_set => { // Payload.decl should be same for all instances of the type. - const payload = @fieldParentPtr(Payload.ErrorSet, "base", self.ptr_otherwise); + const payload = self.castTag(.error_set).?.data; std.hash.autoHash(&hasher, payload.decl); }, .int_type => { - const payload = self.cast(Payload.IntType).?; + const payload = self.castTag(.int_type).?.data; var int_payload = Type.Payload.Bits{ .base = .{ .tag = if (payload.signed) .int_signed else .int_unsigned, @@ -1341,25 +1458,29 @@ pub const Value = extern union { .one, .bool_true => std.hash.autoHash(&hasher, @as(u64, 1)), .float_16, .float_32, .float_64, .float_128 => {}, - .enum_literal, .bytes => { - const payload = @fieldParentPtr(Payload.Bytes, "base", self.ptr_otherwise); + .enum_literal => { + const payload = self.castTag(.enum_literal).?; + hasher.update(payload.data); + }, + .bytes => { + const payload = self.castTag(.bytes).?; hasher.update(payload.data); }, .int_u64 => { - const payload = @fieldParentPtr(Payload.Int_u64, "base", self.ptr_otherwise); - std.hash.autoHash(&hasher, payload.int); + const payload = self.castTag(.int_u64).?; + std.hash.autoHash(&hasher, payload.data); }, .int_i64 => { - const payload = @fieldParentPtr(Payload.Int_i64, "base", self.ptr_otherwise); - std.hash.autoHash(&hasher, payload.int); + const payload = self.castTag(.int_i64).?; + std.hash.autoHash(&hasher, payload.data); }, .repeated => { - const payload = @fieldParentPtr(Payload.Repeated, "base", self.ptr_otherwise); - std.hash.autoHash(&hasher, payload.val.hash()); + const payload = self.castTag(.repeated).?; + std.hash.autoHash(&hasher, payload.data.hash()); }, .ref_val => { - const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise); - std.hash.autoHash(&hasher, payload.val.hash()); + const payload = self.castTag(.ref_val).?; + std.hash.autoHash(&hasher, payload.data.hash()); }, .int_big_positive, .int_big_negative => { var space: BigIntSpace = undefined; @@ -1379,28 +1500,28 @@ pub const Value = extern union { } }, .elem_ptr => { - const payload = @fieldParentPtr(Payload.ElemPtr, "base", self.ptr_otherwise); + const payload = self.castTag(.elem_ptr).?.data; std.hash.autoHash(&hasher, payload.array_ptr.hash()); std.hash.autoHash(&hasher, payload.index); }, .decl_ref => { - const payload = @fieldParentPtr(Payload.DeclRef, "base", self.ptr_otherwise); - std.hash.autoHash(&hasher, payload.decl); + const decl = self.castTag(.decl_ref).?.data; + std.hash.autoHash(&hasher, decl); }, .function => { - const payload = @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise); - std.hash.autoHash(&hasher, payload.func); + const func = self.castTag(.function).?.data; + std.hash.autoHash(&hasher, func); }, .extern_fn => { - const payload = @fieldParentPtr(Payload.ExternFn, "base", self.ptr_otherwise); - std.hash.autoHash(&hasher, payload.decl); + const decl = self.castTag(.extern_fn).?.data; + std.hash.autoHash(&hasher, decl); }, .variable => { - const payload = @fieldParentPtr(Payload.Variable, "base", self.ptr_otherwise); - std.hash.autoHash(&hasher, payload.variable); + const variable = self.castTag(.variable).?.data; + std.hash.autoHash(&hasher, variable); }, .@"error" => { - const payload = @fieldParentPtr(Payload.Error, "base", self.ptr_otherwise); + const payload = self.castTag(.@"error").?.data; hasher.update(payload.name); std.hash.autoHash(&hasher, payload.value); }, @@ -1483,10 +1604,10 @@ pub const Value = extern union { .empty_struct_value, => unreachable, - .ref_val => self.cast(Payload.RefVal).?.val, - .decl_ref => self.cast(Payload.DeclRef).?.decl.value(), + .ref_val => self.castTag(.ref_val).?.data, + .decl_ref => self.castTag(.decl_ref).?.data.value(), .elem_ptr => { - const elem_ptr = self.cast(Payload.ElemPtr).?; + const elem_ptr = self.castTag(.elem_ptr).?.data; const array_val = try elem_ptr.array_ptr.pointerDeref(allocator); return array_val.elemValue(allocator, elem_ptr.index); }, @@ -1570,26 +1691,26 @@ pub const Value = extern union { .empty_array => unreachable, // out of bounds array index - .bytes => { - const int_payload = try allocator.create(Payload.Int_u64); - int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] }; - return Value.initPayload(&int_payload.base); - }, + .bytes => return Tag.int_u64.create(allocator, self.castTag(.bytes).?.data[index]), // No matter the index; all the elements are the same! - .repeated => return self.cast(Payload.Repeated).?.val, + .repeated => return self.castTag(.repeated).?.data, } } /// Returns a pointer to the element value at the index. pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value { - const payload = try allocator.create(Payload.ElemPtr); - if (self.cast(Payload.ElemPtr)) |elem_ptr| { - payload.* = .{ .array_ptr = elem_ptr.array_ptr, .index = elem_ptr.index + index }; - } else { - payload.* = .{ .array_ptr = self, .index = index }; + if (self.castTag(.elem_ptr)) |elem_ptr| { + return Tag.elem_ptr.create(allocator, .{ + .array_ptr = elem_ptr.data.array_ptr, + .index = elem_ptr.data.index + index, + }); } - return Value.initPayload(&payload.base); + + return Tag.elem_ptr.create(allocator, .{ + .array_ptr = self, + .index = index, + }); } pub fn isUndef(self: Value) bool { @@ -1776,131 +1897,128 @@ pub const Value = extern union { pub const Payload = struct { tag: Tag, - pub const Int_u64 = struct { - base: Payload = Payload{ .tag = .int_u64 }, - int: u64, + pub const U64 = struct { + base: Payload, + data: u64, }; - pub const Int_i64 = struct { - base: Payload = Payload{ .tag = .int_i64 }, - int: i64, - }; - - pub const IntBigPositive = struct { - base: Payload = Payload{ .tag = .int_big_positive }, - limbs: []const std.math.big.Limb, - - pub fn asBigInt(self: IntBigPositive) BigIntConst { - return BigIntConst{ .limbs = self.limbs, .positive = true }; - } + pub const I64 = struct { + base: Payload, + data: i64, }; - pub const IntBigNegative = struct { - base: Payload = Payload{ .tag = .int_big_negative }, - limbs: []const std.math.big.Limb, + pub const BigInt = struct { + base: Payload, + data: []const std.math.big.Limb, - pub fn asBigInt(self: IntBigNegative) BigIntConst { - return BigIntConst{ .limbs = self.limbs, .positive = false }; + pub fn asBigInt(self: BigInt) BigIntConst { + const positive = switch (self.base.tag) { + .int_big_positive => true, + .int_big_negative => false, + else => unreachable, + }; + return BigIntConst{ .limbs = self.data, .positive = positive }; } }; pub const Function = struct { - base: Payload = Payload{ .tag = .function }, - func: *Module.Fn, + base: Payload, + data: *Module.Fn, }; - pub const ExternFn = struct { - base: Payload = Payload{ .tag = .extern_fn }, - decl: *Module.Decl, + pub const Decl = struct { + base: Payload, + data: *Module.Decl, }; pub const Variable = struct { - base: Payload = Payload{ .tag = .variable }, - variable: *Module.Var, - }; - - pub const ArraySentinel0_u8_Type = struct { - base: Payload = Payload{ .tag = .array_sentinel_0_u8_type }, - len: u64, - }; - - /// Represents a pointer to another immutable value. - pub const RefVal = struct { - base: Payload = Payload{ .tag = .ref_val }, - val: Value, + base: Payload, + data: *Module.Var, }; - /// Represents a pointer to a decl, not the value of the decl. - pub const DeclRef = struct { - base: Payload = Payload{ .tag = .decl_ref }, - decl: *Module.Decl, + pub const SubValue = struct { + base: Payload, + data: Value, }; pub const ElemPtr = struct { - base: Payload = Payload{ .tag = .elem_ptr }, - array_ptr: Value, - index: usize, + pub const base_tag = Tag.elem_ptr; + + base: Payload = Payload{ .tag = base_tag }, + data: struct { + array_ptr: Value, + index: usize, + }, }; pub const Bytes = struct { - base: Payload = Payload{ .tag = .bytes }, + base: Payload, data: []const u8, }; pub const Ty = struct { - base: Payload = Payload{ .tag = .ty }, - ty: Type, + base: Payload, + data: Type, }; pub const IntType = struct { - base: Payload = Payload{ .tag = .int_type }, - bits: u16, - signed: bool, - }; + pub const base_tag = Tag.int_type; - pub const Repeated = struct { - base: Payload = Payload{ .tag = .ty }, - /// This value is repeated some number of times. The amount of times to repeat - /// is stored externally. - val: Value, + base: Payload = Payload{ .tag = base_tag }, + data: struct { + bits: u16, + signed: bool, + }, }; pub const Float_16 = struct { - base: Payload = .{ .tag = .float_16 }, - val: f16, + pub const base_tag = Tag.float_16; + + base: Payload = .{ .tag = base_tag }, + data: f16, }; pub const Float_32 = struct { - base: Payload = .{ .tag = .float_32 }, - val: f32, + pub const base_tag = Tag.float_32; + + base: Payload = .{ .tag = base_tag }, + data: f32, }; pub const Float_64 = struct { - base: Payload = .{ .tag = .float_64 }, - val: f64, + pub const base_tag = Tag.float_64; + + base: Payload = .{ .tag = base_tag }, + data: f64, }; pub const Float_128 = struct { - base: Payload = .{ .tag = .float_128 }, - val: f128, + pub const base_tag = Tag.float_128; + + base: Payload = .{ .tag = base_tag }, + data: f128, }; pub const ErrorSet = struct { - base: Payload = .{ .tag = .error_set }, + pub const base_tag = Tag.error_set; - // TODO revisit this when we have the concept of the error tag type - fields: std.StringHashMapUnmanaged(u16), - decl: *Module.Decl, + base: Payload = .{ .tag = base_tag }, + data: struct { + // TODO revisit this when we have the concept of the error tag type + fields: std.StringHashMapUnmanaged(u16), + decl: *Module.Decl, + }, }; pub const Error = struct { base: Payload = .{ .tag = .@"error" }, - - // TODO revisit this when we have the concept of the error tag type - /// `name` is owned by `Module` and will be valid for the entire - /// duration of the compilation. - name: []const u8, - value: u16, + data: struct { + // TODO revisit this when we have the concept of the error tag type + /// `name` is owned by `Module` and will be valid for the entire + /// duration of the compilation. + name: []const u8, + value: u16, + }, }; }; @@ -1914,15 +2032,24 @@ pub const Value = extern union { test "hash same value different representation" { const zero_1 = Value.initTag(.zero); - var payload_1 = Value.Payload.Int_u64{ .int = 0 }; + var payload_1 = Value.Payload.U64{ + .base = .{ .tag = .int_u64 }, + .data = 0, + }; const zero_2 = Value.initPayload(&payload_1.base); std.testing.expectEqual(zero_1.hash(), zero_2.hash()); - var payload_2 = Value.Payload.Int_i64{ .int = 0 }; + var payload_2 = Value.Payload.I64{ + .base = .{ .tag = .int_i64 }, + .data = 0, + }; const zero_3 = Value.initPayload(&payload_2.base); std.testing.expectEqual(zero_2.hash(), zero_3.hash()); - var payload_3 = Value.Payload.IntBigNegative{ .limbs = &[_]std.math.big.Limb{0} }; + var payload_3 = Value.Payload.BigInt{ + .base = .{ .tag = .int_big_negative }, + .data = &[_]std.math.big.Limb{0}, + }; const zero_4 = Value.initPayload(&payload_3.base); std.testing.expectEqual(zero_3.hash(), zero_4.hash()); } diff --git a/src/zir.zig b/src/zir.zig index bd9ab2c538..21bd4f8435 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1990,15 +1990,15 @@ const EmitZIR = struct { fn resolveInst(self: *EmitZIR, new_body: ZirBody, inst: *ir.Inst) !*Inst { if (inst.cast(ir.Inst.Constant)) |const_inst| { - const new_inst = if (const_inst.val.cast(Value.Payload.Function)) |func_pl| blk: { - const owner_decl = func_pl.func.owner_decl; + const new_inst = if (const_inst.val.castTag(.function)) |func_pl| blk: { + const owner_decl = func_pl.data.owner_decl; break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name)); - } else if (const_inst.val.cast(Value.Payload.DeclRef)) |declref| blk: { - const decl_ref = try self.emitDeclRef(inst.src, declref.decl); + } else if (const_inst.val.castTag(.decl_ref)) |declref| blk: { + const decl_ref = try self.emitDeclRef(inst.src, declref.data); try new_body.instructions.append(decl_ref); break :blk decl_ref; - } else if (const_inst.val.cast(Value.Payload.Variable)) |var_pl| blk: { - const owner_decl = var_pl.variable.owner_decl; + } else if (const_inst.val.castTag(.variable)) |var_pl| blk: { + const owner_decl = var_pl.data.owner_decl; break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name)); } else blk: { break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst; @@ -2150,13 +2150,13 @@ const EmitZIR = struct { fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl { const allocator = &self.arena.allocator; - if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| { - const decl = decl_ref.decl; + if (typed_value.val.castTag(.decl_ref)) |decl_ref| { + const decl = decl_ref.data; return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl)); - } else if (typed_value.val.cast(Value.Payload.Variable)) |variable| { + } else if (typed_value.val.castTag(.variable)) |variable| { return self.emitTypedValue(src, .{ .ty = typed_value.ty, - .val = variable.variable.init, + .val = variable.data.init, }); } if (typed_value.val.isUndef()) { @@ -2215,7 +2215,7 @@ const EmitZIR = struct { return self.emitType(src, ty); }, .Fn => { - const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; + const module_fn = typed_value.val.castTag(.function).?.data; return self.emitFn(module_fn, src, typed_value.ty); }, .Array => { @@ -2248,7 +2248,7 @@ const EmitZIR = struct { else return self.emitPrimitive(src, .@"false"), .EnumLiteral => { - const enum_literal = @fieldParentPtr(Value.Payload.Bytes, "base", typed_value.val.ptr_otherwise); + const enum_literal = typed_value.val.castTag(.enum_literal).?; const inst = try self.arena.allocator.create(Inst.Str); inst.* = .{ .base = .{ @@ -2748,9 +2748,8 @@ const EmitZIR = struct { .signed => .@"true", .unsigned => .@"false", }); - const bits_payload = try self.arena.allocator.create(Value.Payload.Int_u64); - bits_payload.* = .{ .int = info.bits }; - const bits = try self.emitComptimeIntVal(src, Value.initPayload(&bits_payload.base)); + const bits_val = try Value.Tag.int_u64.create(&self.arena.allocator, info.bits); + const bits = try self.emitComptimeIntVal(src, bits_val); const inttype_inst = try self.arena.allocator.create(Inst.IntType); inttype_inst.* = .{ .base = .{ @@ -2800,7 +2799,10 @@ const EmitZIR = struct { return self.emitUnnamedDecl(&inst.base); }, .Array => { - var len_pl = Value.Payload.Int_u64{ .int = ty.arrayLen() }; + var len_pl = Value.Payload.U64{ + .base = .{ .tag = .int_u64 }, + .data = ty.arrayLen(), + }; const len = Value.initPayload(&len_pl.base); const inst = if (ty.sentinel()) |sentinel| blk: { diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 8960f3ba4d..2e3cced839 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -364,12 +364,9 @@ fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError! const ptr_type = try mod.simplePtrType(scope, inst.base.src, operand.ty, false, .One); if (operand.value()) |val| { - const ref_payload = try scope.arena().create(Value.Payload.RefVal); - ref_payload.* = .{ .val = val }; - return mod.constInst(scope, inst.base.src, .{ .ty = ptr_type, - .val = Value.initPayload(&ref_payload.base), + .val = try Value.Tag.ref_val.create(scope.arena(), val), }); } @@ -480,12 +477,9 @@ fn analyzeInstStr(mod: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerErr errdefer new_decl_arena.deinit(); const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); - const bytes_payload = try scope.arena().create(Value.Payload.Bytes); - bytes_payload.* = .{ .data = arena_bytes }; - const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{ .ty = try Type.Tag.array_u8_sentinel_0.create(scope.arena(), arena_bytes.len), - .val = Value.initPayload(&bytes_payload.base), + .val = try Value.Tag.bytes.create(scope.arena(), arena_bytes), }); return mod.analyzeDeclRef(scope, str_inst.base.src, new_decl); } @@ -779,11 +773,9 @@ fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError! .analysis = .{ .queued = fn_zir }, .owner_decl = scope.decl().?, }; - const fn_payload = try scope.arena().create(Value.Payload.Function); - fn_payload.* = .{ .func = new_func }; return mod.constInst(scope, fn_inst.base.src, .{ .ty = fn_type, - .val = Value.initPayload(&fn_payload.base), + .val = try Value.Tag.function.create(scope.arena(), new_func), }); } @@ -838,14 +830,17 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In const payload = try scope.arena().create(Value.Payload.ErrorSet); payload.* = .{ - .fields = .{}, - .decl = undefined, // populated below + .base = .{ .tag = .error_set }, + .data = .{ + .fields = .{}, + .decl = undefined, // populated below + }, }; - try payload.fields.ensureCapacity(&new_decl_arena.allocator, @intCast(u32, inst.positionals.fields.len)); + try payload.data.fields.ensureCapacity(&new_decl_arena.allocator, @intCast(u32, inst.positionals.fields.len)); for (inst.positionals.fields) |field_name| { const entry = try mod.getErrorValue(field_name); - if (payload.fields.fetchPutAssumeCapacity(entry.key, entry.value)) |prev| { + if (payload.data.fields.fetchPutAssumeCapacity(entry.key, entry.value)) |prev| { return mod.fail(scope, inst.base.src, "duplicate error: '{}'", .{field_name}); } } @@ -854,7 +849,7 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In .ty = Type.initTag(.type), .val = Value.initPayload(&payload.base), }); - payload.decl = new_decl; + payload.data.decl = new_decl; return mod.analyzeDeclRef(scope, inst.base.src, new_decl); } @@ -863,14 +858,10 @@ fn analyzeInstMergeErrorSets(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) } fn analyzeInstEnumLiteral(mod: *Module, scope: *Scope, inst: *zir.Inst.EnumLiteral) InnerError!*Inst { - const payload = try scope.arena().create(Value.Payload.Bytes); - payload.* = .{ - .base = .{ .tag = .enum_literal }, - .data = try scope.arena().dupe(u8, inst.positionals.name), - }; + const duped_name = try scope.arena().dupe(u8, inst.positionals.name); return mod.constInst(scope, inst.base.src, .{ .ty = Type.initTag(.enum_literal), - .val = Value.initPayload(&payload.base), + .val = try Value.Tag.enum_literal.create(scope.arena(), duped_name), }); } @@ -989,15 +980,12 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr switch (elem_ty.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { - const len_payload = try scope.arena().create(Value.Payload.Int_u64); - len_payload.* = .{ .int = elem_ty.arrayLen() }; - - const ref_payload = try scope.arena().create(Value.Payload.RefVal); - ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) }; - return mod.constInst(scope, fieldptr.base.src, .{ .ty = Type.initTag(.single_const_pointer_to_comptime_int), - .val = Value.initPayload(&ref_payload.base), + .val = try Value.Tag.ref_val.create( + scope.arena(), + try Value.Tag.int_u64.create(scope.arena(), elem_ty.arrayLen()), + ), }); } else { return mod.fail( @@ -1013,15 +1001,12 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr switch (ptr_child.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { - const len_payload = try scope.arena().create(Value.Payload.Int_u64); - len_payload.* = .{ .int = ptr_child.arrayLen() }; - - const ref_payload = try scope.arena().create(Value.Payload.RefVal); - ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) }; - return mod.constInst(scope, fieldptr.base.src, .{ .ty = Type.initTag(.single_const_pointer_to_comptime_int), - .val = Value.initPayload(&ref_payload.base), + .val = try Value.Tag.ref_val.create( + scope.arena(), + try Value.Tag.int_u64.create(scope.arena(), ptr_child.arrayLen()), + ), }); } else { return mod.fail( @@ -1043,21 +1028,12 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr switch (child_type.zigTypeTag()) { .ErrorSet => { // TODO resolve inferred error sets - const entry = if (val.cast(Value.Payload.ErrorSet)) |payload| - (payload.fields.getEntry(field_name) orelse + const entry = if (val.castTag(.error_set)) |payload| + (payload.data.fields.getEntry(field_name) orelse return mod.fail(scope, fieldptr.base.src, "no error named '{}' in '{}'", .{ field_name, child_type })).* else try mod.getErrorValue(field_name); - const error_payload = try scope.arena().create(Value.Payload.Error); - error_payload.* = .{ - .name = entry.key, - .value = entry.value, - }; - - const ref_payload = try scope.arena().create(Value.Payload.RefVal); - ref_payload.* = .{ .val = Value.initPayload(&error_payload.base) }; - const result_type = if (child_type.tag() == .anyerror) try Type.Tag.error_set_single.create(scope.arena(), entry.key) else @@ -1065,7 +1041,13 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr return mod.constInst(scope, fieldptr.base.src, .{ .ty = try mod.simplePtrType(scope, fieldptr.base.src, result_type, false, .One), - .val = Value.initPayload(&ref_payload.base), + .val = try Value.Tag.ref_val.create( + scope.arena(), + try Value.Tag.@"error".create(scope.arena(), .{ + .name = entry.key, + .value = entry.value, + }), + ), }); }, .Struct => { -- cgit v1.2.3 From a46d24af1cf2885991c67bf39a2e639891c16121 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 31 Dec 2020 01:54:02 -0700 Subject: stage2: inferred local variables This patch introduces the following new things: Types: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support most of the normal type queries. However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc. - The payload for this type simply points to the corresponding Value payload. Values: - inferred_alloc - This is a special value that tracks a set of types that have been stored to an inferred allocation. It does not support any of the normal value queries. ZIR instructions: - store_to_inferred_ptr, - Same as `store` but the type of the value being stored will be used to infer the pointer type. - resolve_inferred_alloc - Each `store_to_inferred_ptr` puts the type of the stored value into a set, and then `resolve_inferred_alloc` triggers peer type resolution on the set. The operand is a `alloc_inferred` or `alloc_inferred_mut` instruction, which is the allocation that needs to have its type inferred. Changes to the C backend: * Implements the bitcast instruction. If the source and dest types are both pointers, uses a cast, otherwise uses memcpy. * Tests are run with -Wno-declaration-after-statement. Someday we can conform to this but not today. In ZIR form it looks like this: ```zir fn_body main { // unanalyzed %0 = dbg_stmt() =>%1 = alloc_inferred() %2 = declval_in_module(Decl(add)) %3 = deref(%2) %4 = param_type(%3, 0) %5 = const(TypedValue{ .ty = comptime_int, .val = 1}) %6 = as(%4, %5) %7 = param_type(%3, 1) %8 = const(TypedValue{ .ty = comptime_int, .val = 2}) %9 = as(%7, %8) %10 = call(%3, [%6, %9], modifier=auto) =>%11 = store_to_inferred_ptr(%1, %10) =>%12 = resolve_inferred_alloc(%1) %13 = dbg_stmt() %14 = ret_type() %15 = const(TypedValue{ .ty = comptime_int, .val = 3}) %16 = sub(%10, %15) %17 = as(%14, %16) %18 = return(%17) } // fn_body main ``` I have not played around with very many test cases yet. Some interesting ones that I want to look at before merging: ```zig var x = blk: { var y = foo(); y.a = 1; break :blk y; }; ``` In the above test case, x and y are supposed to alias. ```zig var x = if (bar()) blk: { var y = foo(); y.a = 1; break :blk y; } else blk: { var z = baz(); z.b = 1; break :blk z; }; ``` In the above test case, x, y, and z are supposed to alias. I also haven't tested with `var` instead of `const` yet. --- src/Module.zig | 9 +++- src/astgen.zig | 13 ++++- src/codegen/c.zig | 20 +++++++ src/ir.zig | 2 +- src/link/cbe.h | 2 +- src/test.zig | 1 + src/type.zig | 148 ++++++++++++++++++++++++++++++++++------------------ src/value.zig | 36 +++++++++++++ src/zir.zig | 12 +++++ src/zir_sema.zig | 61 +++++++++++++++++++++- test/stage2/cbe.zig | 15 ++++++ 11 files changed, 261 insertions(+), 58 deletions(-) (limited to 'src/codegen') diff --git a/src/Module.zig b/src/Module.zig index ca0718c3d5..884b5fb8d4 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3189,7 +3189,14 @@ pub fn floatSub( } } -pub fn simplePtrType(self: *Module, scope: *Scope, src: usize, elem_ty: Type, mutable: bool, size: std.builtin.TypeInfo.Pointer.Size) Allocator.Error!Type { +pub fn simplePtrType( + self: *Module, + scope: *Scope, + src: usize, + elem_ty: Type, + mutable: bool, + size: std.builtin.TypeInfo.Pointer.Size, +) Allocator.Error!Type { if (!mutable and size == .Slice and elem_ty.eql(Type.initTag(.u8))) { return Type.initTag(.const_slice_u8); } diff --git a/src/astgen.zig b/src/astgen.zig index c5261c4073..2ce21dbab4 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -585,6 +585,7 @@ fn varDecl( switch (tree.token_ids[node.mut_token]) { .Keyword_const => { + var resolve_inferred_alloc: ?*zir.Inst = null; // Depending on the type of AST the initialization expression is, we may need an lvalue // or an rvalue as a result location. If it is an rvalue, we can use the instruction as // the variable, no memory location needed. @@ -595,6 +596,7 @@ fn varDecl( break :r ResultLoc{ .ptr = alloc }; } else { const alloc = try addZIRNoOpT(mod, scope, name_src, .alloc_inferred); + resolve_inferred_alloc = &alloc.base; break :r ResultLoc{ .inferred_ptr = alloc }; } } else r: { @@ -604,6 +606,9 @@ fn varDecl( break :r .none; }; const init_inst = try expr(mod, scope, result_loc, init_node); + if (resolve_inferred_alloc) |inst| { + _ = try addZIRUnOp(mod, scope, name_src, .resolve_inferred_alloc, inst); + } const sub_scope = try block_arena.create(Scope.LocalVal); sub_scope.* = .{ .parent = scope, @@ -614,15 +619,20 @@ fn varDecl( return &sub_scope.base; }, .Keyword_var => { + var resolve_inferred_alloc: ?*zir.Inst = null; const var_data: struct { result_loc: ResultLoc, alloc: *zir.Inst } = if (node.getTypeNode()) |type_node| a: { const type_inst = try typeExpr(mod, scope, type_node); const alloc = try addZIRUnOp(mod, scope, name_src, .alloc_mut, type_inst); break :a .{ .alloc = alloc, .result_loc = .{ .ptr = alloc } }; } else a: { const alloc = try addZIRNoOp(mod, scope, name_src, .alloc_inferred_mut); + resolve_inferred_alloc = alloc; break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred_mut).? } }; }; const init_inst = try expr(mod, scope, var_data.result_loc, init_node); + if (resolve_inferred_alloc) |inst| { + _ = try addZIRUnOp(mod, scope, name_src, .resolve_inferred_alloc, inst); + } const sub_scope = try block_arena.create(Scope.LocalPtr); sub_scope.* = .{ .parent = scope, @@ -2717,7 +2727,8 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr return mod.fail(scope, result.src, "TODO implement rlWrap .bitcasted_ptr", .{}); }, .inferred_ptr => |alloc| { - return addZIRBinOp(mod, scope, result.src, .store, &alloc.base, result); + _ = try addZIRBinOp(mod, scope, result.src, .store_to_inferred_ptr, &alloc.base, result); + return result; }, .block_ptr => |block_ptr| { return mod.fail(scope, result.src, "TODO implement rlWrap .block_ptr", .{}); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 7cd4479bd9..6d9563b991 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -275,6 +275,7 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { try writer.writeAll(" {"); const func: *Module.Fn = func_payload.data; + //func.dump(module.*); const instructions = func.analysis.success.instructions; if (instructions.len > 0) { try writer.writeAll("\n"); @@ -285,6 +286,7 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { .arg => try genArg(&ctx), .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), .block => try genBlock(&ctx, file, inst.castTag(.block).?), + .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?), .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), .call => try genCall(&ctx, file, inst.castTag(.call).?), .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="), @@ -537,6 +539,24 @@ fn genBlock(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 { return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{}); } +fn genBitcast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { + const writer = file.main.writer(); + try indent(file); + const local_name = try ctx.name(); + const operand = try ctx.resolveInst(inst.operand); + try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); + if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { + try writer.writeAll(" = ("); + try renderType(ctx, writer, inst.base.ty); + try writer.print("){s};\n", .{operand}); + } else { + try writer.writeAll(";\n"); + try indent(file); + try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name }); + } + return local_name; +} + fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 { try indent(file); try file.main.writer().writeAll("zig_breakpoint();\n"); diff --git a/src/ir.zig b/src/ir.zig index fc29323247..dda625c735 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -196,7 +196,7 @@ pub const Inst = struct { pub fn value(base: *Inst) ?Value { if (base.ty.onePossibleValue()) |opv| return opv; - const inst = base.cast(Constant) orelse return null; + const inst = base.castTag(.constant) orelse return null; return inst.val; } diff --git a/src/link/cbe.h b/src/link/cbe.h index e62e6766ef..8452af8fbc 100644 --- a/src/link/cbe.h +++ b/src/link/cbe.h @@ -41,4 +41,4 @@ #include #define int128_t __int128 #define uint128_t unsigned __int128 - +#include diff --git a/src/test.zig b/src/test.zig index b996a25c78..6d76ae39c1 100644 --- a/src/test.zig +++ b/src/test.zig @@ -782,6 +782,7 @@ pub const TestContext = struct { "-std=c89", "-pedantic", "-Werror", + "-Wno-declaration-after-statement", "--", "-lc", exe_path, diff --git a/src/type.zig b/src/type.zig index 9d834a19f2..ea21be0e68 100644 --- a/src/type.zig +++ b/src/type.zig @@ -78,6 +78,7 @@ pub const Type = extern union { .const_slice, .mut_slice, .pointer, + .inferred_alloc, => return .Pointer, .optional, @@ -158,6 +159,8 @@ pub const Type = extern union { .optional_single_mut_pointer, => self.cast(Payload.ElemType), + .inferred_alloc => unreachable, + else => null, }; } @@ -384,6 +387,7 @@ pub const Type = extern union { .enum_literal, .anyerror_void_error_union, .@"anyframe", + .inferred_alloc, => unreachable, .array_u8, @@ -686,6 +690,7 @@ pub const Type = extern union { const name = ty.castTag(.error_set_single).?.data; return out_stream.print("error{{{s}}}", .{name}); }, + .inferred_alloc => return out_stream.writeAll("(inferred allocation type)"), } unreachable; } @@ -733,6 +738,7 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type), .const_slice_u8 => return Value.initTag(.const_slice_u8_type), .enum_literal => return Value.initTag(.enum_literal_type), + .inferred_alloc => unreachable, else => return Value.Tag.ty.create(allocator, self), } } @@ -803,6 +809,8 @@ pub const Type = extern union { .enum_literal, .empty_struct, => false, + + .inferred_alloc => unreachable, }; } @@ -920,6 +928,7 @@ pub const Type = extern union { .@"undefined", .enum_literal, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -943,6 +952,7 @@ pub const Type = extern union { .enum_literal => unreachable, .single_const_pointer_to_comptime_int => unreachable, .empty_struct => unreachable, + .inferred_alloc => unreachable, .u8, .i8, @@ -1121,6 +1131,7 @@ pub const Type = extern union { .single_const_pointer, .single_mut_pointer, .single_const_pointer_to_comptime_int, + .inferred_alloc, => true, .pointer => self.castTag(.pointer).?.data.size == .One, @@ -1203,6 +1214,7 @@ pub const Type = extern union { .single_const_pointer, .single_mut_pointer, .single_const_pointer_to_comptime_int, + .inferred_alloc, => .One, .pointer => self.castTag(.pointer).?.data.size, @@ -1273,6 +1285,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .const_slice, @@ -1345,6 +1358,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .single_const_pointer, @@ -1426,6 +1440,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .pointer => { @@ -1502,6 +1517,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .pointer => { @@ -1569,58 +1585,58 @@ pub const Type = extern union { /// Asserts the type is a pointer or array type. pub fn elemType(self: Type) Type { return switch (self.tag()) { - .u8, - .i8, - .u16, - .i16, - .u32, - .i32, - .u64, - .i64, - .usize, - .isize, - .c_short, - .c_ushort, - .c_int, - .c_uint, - .c_long, - .c_ulong, - .c_longlong, - .c_ulonglong, - .c_longdouble, - .f16, - .f32, - .f64, - .f128, - .c_void, - .bool, - .void, - .type, - .anyerror, - .comptime_int, - .comptime_float, - .noreturn, - .@"null", - .@"undefined", - .fn_noreturn_no_args, - .fn_void_no_args, - .fn_naked_noreturn_no_args, - .fn_ccc_void_no_args, - .function, - .int_unsigned, - .int_signed, - .optional, - .optional_single_const_pointer, - .optional_single_mut_pointer, - .enum_literal, - .error_union, - .@"anyframe", - .anyframe_T, - .anyerror_void_error_union, - .error_set, - .error_set_single, - .empty_struct, - => unreachable, + .u8 => unreachable, + .i8 => unreachable, + .u16 => unreachable, + .i16 => unreachable, + .u32 => unreachable, + .i32 => unreachable, + .u64 => unreachable, + .i64 => unreachable, + .usize => unreachable, + .isize => unreachable, + .c_short => unreachable, + .c_ushort => unreachable, + .c_int => unreachable, + .c_uint => unreachable, + .c_long => unreachable, + .c_ulong => unreachable, + .c_longlong => unreachable, + .c_ulonglong => unreachable, + .c_longdouble => unreachable, + .f16 => unreachable, + .f32 => unreachable, + .f64 => unreachable, + .f128 => unreachable, + .c_void => unreachable, + .bool => unreachable, + .void => unreachable, + .type => unreachable, + .anyerror => unreachable, + .comptime_int => unreachable, + .comptime_float => unreachable, + .noreturn => unreachable, + .@"null" => unreachable, + .@"undefined" => unreachable, + .fn_noreturn_no_args => unreachable, + .fn_void_no_args => unreachable, + .fn_naked_noreturn_no_args => unreachable, + .fn_ccc_void_no_args => unreachable, + .function => unreachable, + .int_unsigned => unreachable, + .int_signed => unreachable, + .optional => unreachable, + .optional_single_const_pointer => unreachable, + .optional_single_mut_pointer => unreachable, + .enum_literal => unreachable, + .error_union => unreachable, + .@"anyframe" => unreachable, + .anyframe_T => unreachable, + .anyerror_void_error_union => unreachable, + .error_set => unreachable, + .error_set_single => unreachable, + .empty_struct => unreachable, + .inferred_alloc => unreachable, .array => self.castTag(.array).?.data.elem_type, .array_sentinel => self.castTag(.array_sentinel).?.data.elem_type, @@ -1742,6 +1758,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, .array => self.castTag(.array).?.data.len, @@ -1808,6 +1825,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, .single_const_pointer, @@ -1891,6 +1909,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .int_signed, @@ -1966,6 +1985,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .int_unsigned, @@ -2031,6 +2051,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, .int_unsigned => .{ @@ -2120,6 +2141,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, .usize, @@ -2232,6 +2254,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -2310,6 +2333,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, } } @@ -2387,6 +2411,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, } } @@ -2464,6 +2489,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -2538,6 +2564,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -2612,6 +2639,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => unreachable, }; } @@ -2686,6 +2714,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => false, }; } @@ -2778,6 +2807,7 @@ pub const Type = extern union { ty = ty.castTag(.pointer).?.data.pointee_type; continue; }, + .inferred_alloc => unreachable, }; } @@ -2846,6 +2876,7 @@ pub const Type = extern union { .error_set, .error_set_single, .empty_struct, + .inferred_alloc, => return false, .c_const_pointer, @@ -2931,6 +2962,7 @@ pub const Type = extern union { .c_const_pointer, .c_mut_pointer, .pointer, + .inferred_alloc, => unreachable, .empty_struct => self.castTag(.empty_struct).?.data, @@ -3068,6 +3100,10 @@ pub const Type = extern union { error_set, error_set_single, empty_struct, + /// This is a special value that tracks a set of types that have been stored + /// to an inferred allocation. It does not support most of the normal type queries. + /// However it does respond to `isConstPtr`, `ptrSize`, `zigTypeTag`, etc. + inferred_alloc, pub const last_no_payload_tag = Tag.const_slice_u8; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -3148,6 +3184,7 @@ pub const Type = extern union { .error_set => Payload.Decl, .error_set_single => Payload.Name, .empty_struct => Payload.ContainerScope, + .inferred_alloc => Payload.InferredAlloc, }; } @@ -3261,6 +3298,13 @@ pub const Type = extern union { base: Payload, data: *Module.Scope.Container, }; + + pub const InferredAlloc = struct { + pub const base_tag = Tag.inferred_alloc; + + base: Payload = .{ .tag = base_tag }, + data: *Value.Payload.InferredAlloc, + }; }; }; diff --git a/src/value.zig b/src/value.zig index 91b21511d4..4450099069 100644 --- a/src/value.zig +++ b/src/value.zig @@ -7,6 +7,7 @@ const BigIntMutable = std.math.big.int.Mutable; const Target = std.Target; const Allocator = std.mem.Allocator; const Module = @import("Module.zig"); +const ir = @import("ir.zig"); /// This is the raw data, with no bookkeeping, no memory awareness, /// no de-duplication, and no type system awareness. @@ -101,6 +102,9 @@ pub const Value = extern union { enum_literal, error_set, @"error", + /// This is a special value that tracks a set of types that have been stored + /// to an inferred allocation. It does not support any of the normal value queries. + inferred_alloc, pub const last_no_payload_tag = Tag.bool_false; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -189,6 +193,7 @@ pub const Value = extern union { .float_128 => Payload.Float_128, .error_set => Payload.ErrorSet, .@"error" => Payload.Error, + .inferred_alloc => Payload.InferredAlloc, }; } @@ -383,6 +388,8 @@ pub const Value = extern union { // memory is managed by the declaration .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet), + + .inferred_alloc => unreachable, } } @@ -501,6 +508,7 @@ pub const Value = extern union { return out_stream.writeAll("}"); }, .@"error" => return out_stream.print("error.{}", .{val.castTag(.@"error").?.data.name}), + .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"), }; } @@ -613,6 +621,7 @@ pub const Value = extern union { .enum_literal, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, }; } @@ -683,6 +692,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .undef => unreachable, @@ -768,6 +778,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .undef => unreachable, @@ -853,6 +864,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .undef => unreachable, @@ -966,6 +978,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .zero, @@ -1055,6 +1068,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .zero, @@ -1213,6 +1227,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .zero, @@ -1289,6 +1304,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .zero, @@ -1525,6 +1541,8 @@ pub const Value = extern union { hasher.update(payload.name); std.hash.autoHash(&hasher, payload.value); }, + + .inferred_alloc => unreachable, } return hasher.final(); } @@ -1602,6 +1620,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .ref_val => self.castTag(.ref_val).?.data, @@ -1687,6 +1706,7 @@ pub const Value = extern union { .error_set, .@"error", .empty_struct_value, + .inferred_alloc, => unreachable, .empty_array => unreachable, // out of bounds array index @@ -1793,6 +1813,7 @@ pub const Value = extern union { .undef => unreachable, .unreachable_value => unreachable, + .inferred_alloc => unreachable, .null_value => true, }; } @@ -1801,6 +1822,7 @@ pub const Value = extern union { pub fn isFloat(self: Value) bool { return switch (self.tag()) { .undef => unreachable, + .inferred_alloc => unreachable, .float_16, .float_32, @@ -1890,6 +1912,7 @@ pub const Value = extern union { .undef => unreachable, .unreachable_value => unreachable, + .inferred_alloc => unreachable, }; } @@ -2020,6 +2043,19 @@ pub const Value = extern union { value: u16, }, }; + + pub const InferredAlloc = struct { + pub const base_tag = Tag.inferred_alloc; + + base: Payload = .{ .tag = base_tag }, + data: struct { + /// The value stored in the inferred allocation. This will go into + /// peer type resolution. This is stored in a separate list so that + /// the items are contiguous in memory and thus can be passed to + /// `Module.resolvePeerTypes`. + stored_inst_list: std.ArrayListUnmanaged(*ir.Inst) = .{}, + }, + }; }; /// Big enough to fit any non-BigInt value diff --git a/src/zir.zig b/src/zir.zig index 21bd4f8435..ce8498d7e8 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -241,12 +241,20 @@ pub const Inst = struct { const_slice_type, /// Create a pointer type with attributes ptr_type, + /// Each `store_to_inferred_ptr` puts the type of the stored value into a set, + /// and then `resolve_inferred_alloc` triggers peer type resolution on the set. + /// The operand is a `alloc_inferred` or `alloc_inferred_mut` instruction, which + /// is the allocation that needs to have its type inferred. + resolve_inferred_alloc, /// Slice operation `array_ptr[start..end:sentinel]` slice, /// Slice operation with just start `lhs[rhs..]` slice_start, /// Write a value to a pointer. For loading, see `deref`. store, + /// Same as `store` but the type of the value being stored will be used to infer + /// the pointer type. + store_to_inferred_ptr, /// String Literal. Makes an anonymous Decl and then takes a pointer to it. str, /// Arithmetic subtraction. Asserts no integer overflow. @@ -319,6 +327,7 @@ pub const Inst = struct { .ref, .bitcast_ref, .typeof, + .resolve_inferred_alloc, .single_const_ptr_type, .single_mut_ptr_type, .many_const_ptr_type, @@ -355,6 +364,7 @@ pub const Inst = struct { .shl, .shr, .store, + .store_to_inferred_ptr, .sub, .subwrap, .cmp_lt, @@ -498,6 +508,7 @@ pub const Inst = struct { .mut_slice_type, .const_slice_type, .store, + .store_to_inferred_ptr, .str, .sub, .subwrap, @@ -522,6 +533,7 @@ pub const Inst = struct { .import, .switch_range, .typeof_peer, + .resolve_inferred_alloc, => false, .@"break", diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 2e3cced839..b6c17a2195 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -10,10 +10,12 @@ const std = @import("std"); const mem = std.mem; const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const log = std.log.scoped(.sema); + const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const TypedValue = @import("TypedValue.zig"); -const assert = std.debug.assert; const ir = @import("ir.zig"); const zir = @import("zir.zig"); const Module = @import("Module.zig"); @@ -55,8 +57,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?), .ensure_indexable => return analyzeInstEnsureIndexable(mod, scope, old_inst.castTag(.ensure_indexable).?), .ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?), + .resolve_inferred_alloc => return analyzeInstResolveInferredAlloc(mod, scope, old_inst.castTag(.resolve_inferred_alloc).?), .ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?), .ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?), + .store_to_inferred_ptr => return analyzeInstStoreToInferredPtr(mod, scope, old_inst.castTag(.store_to_inferred_ptr).?), .single_const_ptr_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.single_const_ptr_type).?, false, .One), .single_mut_ptr_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.single_mut_ptr_type).?, true, .One), .many_const_ptr_type => return analyzeInstSimplePtrType(mod, scope, old_inst.castTag(.many_const_ptr_type).?, false, .Many), @@ -428,13 +432,66 @@ fn analyzeInstAllocMut(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerE } fn analyzeInstAllocInferred(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); + const val_payload = try scope.arena().create(Value.Payload.InferredAlloc); + val_payload.* = .{ + .data = .{}, + }; + // `Module.constInst` does not add the instruction to the block because it is + // not needed in the case of constant values. However here, we plan to "downgrade" + // to a normal instruction when we hit `resolve_inferred_alloc`. So we append + // to the block even though it is currently a `.constant`. + const result = try mod.constInst(scope, inst.base.src, .{ + .ty = try Type.Tag.inferred_alloc.create(scope.arena(), val_payload), + .val = Value.initPayload(&val_payload.base), + }); + const block = try mod.requireFunctionBlock(scope, inst.base.src); + try block.instructions.append(mod.gpa, result); + return result; } fn analyzeInstAllocInferredMut(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferredMut", .{}); } +fn analyzeInstResolveInferredAlloc( + mod: *Module, + scope: *Scope, + inst: *zir.Inst.UnOp, +) InnerError!*Inst { + const ptr = try resolveInst(mod, scope, inst.positionals.operand); + const ptr_val = ptr.castTag(.constant).?.val; + const inferred_alloc = ptr_val.castTag(.inferred_alloc).?; + const peer_inst_list = inferred_alloc.data.stored_inst_list.items; + const final_elem_ty = try mod.resolvePeerTypes(scope, peer_inst_list); + const is_mut = true; + const final_ptr_ty = try mod.simplePtrType(scope, inst.base.src, final_elem_ty, is_mut, .One); + + // Change it to a normal alloc. + ptr.ty = final_ptr_ty; + ptr.tag = .alloc; + + return mod.constVoid(scope, inst.base.src); +} + +fn analyzeInstStoreToInferredPtr( + mod: *Module, + scope: *Scope, + inst: *zir.Inst.BinOp, +) InnerError!*Inst { + const ptr = try resolveInst(mod, scope, inst.positionals.lhs); + const value = try resolveInst(mod, scope, inst.positionals.rhs); + const inferred_alloc = ptr.castTag(.constant).?.val.castTag(.inferred_alloc).?; + // Add the stored instruction to the set we will use to resolve peer types + // for the inferred allocation. + try inferred_alloc.data.stored_inst_list.append(scope.arena(), value); + // Create a new alloc with exactly the type the pointer wants. + // Later it gets cleaned up by aliasing the alloc we are supposed to be storing to. + const ptr_ty = try mod.simplePtrType(scope, inst.base.src, value.ty, true, .One); + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + const bitcasted_ptr = try mod.addUnOp(b, inst.base.src, ptr_ty, .bitcast, ptr); + return mod.storePtr(scope, inst.base.src, bitcasted_ptr, value); +} + fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { const ptr = try resolveInst(mod, scope, inst.positionals.lhs); const value = try resolveInst(mod, scope, inst.positionals.rhs); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 0d2a6d4aec..49659276b3 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -51,6 +51,21 @@ pub fn addCases(ctx: *TestContext) !void { , ""); } + { + var case = ctx.exeFromCompiledC("inferred local const", .{}); + + case.addCompareOutput( + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\ + \\export fn main() c_int { + \\ const x = add(1, 2); + \\ return x - 3; + \\} + , ""); + } + ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable; -- cgit v1.2.3 From 7deb1f4f6c038eaef815d5d179633683a9574ff0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 31 Dec 2020 02:42:48 -0700 Subject: stage2: type inference for local var --- BRANCH_TODO | 2 ++ src/astgen.zig | 6 +++--- src/codegen/c.zig | 13 +++++++++++-- test/stage2/cbe.zig | 6 ++++-- 4 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 BRANCH_TODO (limited to 'src/codegen') diff --git a/BRANCH_TODO b/BRANCH_TODO new file mode 100644 index 0000000000..4ba5d950aa --- /dev/null +++ b/BRANCH_TODO @@ -0,0 +1,2 @@ + * no need for payload on inferred_alloc for the type + * compile error for "variable of type '{}' must be const or comptime" after resolving types diff --git a/src/astgen.zig b/src/astgen.zig index 2ce21dbab4..1142907faf 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -625,9 +625,9 @@ fn varDecl( const alloc = try addZIRUnOp(mod, scope, name_src, .alloc_mut, type_inst); break :a .{ .alloc = alloc, .result_loc = .{ .ptr = alloc } }; } else a: { - const alloc = try addZIRNoOp(mod, scope, name_src, .alloc_inferred_mut); - resolve_inferred_alloc = alloc; - break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred_mut).? } }; + const alloc = try addZIRNoOpT(mod, scope, name_src, .alloc_inferred); + resolve_inferred_alloc = &alloc.base; + break :a .{ .alloc = &alloc.base, .result_loc = .{ .inferred_ptr = alloc } }; }; const init_inst = try expr(mod, scope, var_data.result_loc, init_node); if (resolve_inferred_alloc) |inst| { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 6d9563b991..c6c29942d9 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -275,7 +275,6 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { try writer.writeAll(" {"); const func: *Module.Fn = func_payload.data; - //func.dump(module.*); const instructions = func.analysis.success.instructions; if (instructions.len > 0) { try writer.writeAll("\n"); @@ -297,6 +296,7 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="), .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), + .load => try genLoad(&ctx, file, inst.castTag(.load).?), .ret => try genRet(&ctx, file, inst.castTag(.ret).?), .retvoid => try genRetVoid(file), .store => try genStore(&ctx, file, inst.castTag(.store).?), @@ -431,6 +431,16 @@ fn genRetVoid(file: *C) !?[]u8 { return null; } +fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { + const operand = try ctx.resolveInst(inst.operand); + const writer = file.main.writer(); + try indent(file); + const local_name = try ctx.name(); + try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); + try writer.print(" = *{s};\n", .{operand}); + return local_name; +} + fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { try indent(file); const writer = file.main.writer(); @@ -442,7 +452,6 @@ fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { if (inst.base.isUnused()) return null; try indent(file); - const op = inst.operand; const writer = file.main.writer(); const name = try ctx.name(); const from = try ctx.resolveInst(inst.operand); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 49659276b3..a0a4587983 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -52,7 +52,7 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.exeFromCompiledC("inferred local const", .{}); + var case = ctx.exeFromCompiledC("inferred local const and var", .{}); case.addCompareOutput( \\fn add(a: i32, b: i32) i32 { @@ -61,7 +61,9 @@ pub fn addCases(ctx: *TestContext) !void { \\ \\export fn main() c_int { \\ const x = add(1, 2); - \\ return x - 3; + \\ var y = add(3, 0); + \\ y -= x; + \\ return y; \\} , ""); } -- cgit v1.2.3 From c52ca0b1780c2865cb0c242cb2f1a397766e6ce8 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Mon, 28 Dec 2020 21:09:48 +0100 Subject: stage2 ARM: implement genSetReg with compare_flags --- src/codegen.zig | 41 ++++++++++++++++++--------------- src/codegen/arm.zig | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 19 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen.zig b/src/codegen.zig index 4a02a8ffc0..66aa32b14e 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -658,6 +658,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const mcv = try self.genFuncInst(inst); if (!inst.isUnused()) { + log.debug("{*} => {}", .{ inst, mcv }); const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; try branch.inst_table.putNoClobber(self.gpa, inst, mcv); } @@ -2039,27 +2040,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const condition: Condition = switch (cond) { .compare_flags_signed => |cmp_op| blk: { // Here we map to the opposite condition because the jump is to the false branch. - const condition: Condition = switch (cmp_op) { - .gte => .lt, - .gt => .le, - .neq => .eq, - .lt => .ge, - .lte => .gt, - .eq => .ne, - }; - break :blk condition; + const condition = Condition.fromCompareOperatorSigned(cmp_op); + break :blk condition.negate(); }, .compare_flags_unsigned => |cmp_op| blk: { // Here we map to the opposite condition because the jump is to the false branch. - const condition: Condition = switch (cmp_op) { - .gte => .cc, - .gt => .ls, - .neq => .eq, - .lt => .cs, - .lte => .hi, - .eq => .ne, - }; - break :blk condition; + const condition = Condition.fromCompareOperatorUnsigned(cmp_op); + break :blk condition.negate(); }, .register => |reg| blk: { // cmp reg, 1 @@ -2239,7 +2226,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } }, .arm, .armeb => { - if (math.cast(i26, @intCast(i32, index) - @intCast(i32, self.code.items.len))) |delta| { + if (math.cast(i26, @intCast(i32, index) - @intCast(i32, self.code.items.len + 8))) |delta| { writeInt(u32, try self.code.addManyAsArray(4), Instruction.b(.al, delta).toU32()); } else |err| { return self.fail(src, "TODO: enable larger branch offset", .{}); @@ -2736,6 +2723,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // Write the debug undefined value. return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }); }, + .compare_flags_unsigned, + .compare_flags_signed, + => |op| { + const condition = switch (mcv) { + .compare_flags_unsigned => Condition.fromCompareOperatorUnsigned(op), + .compare_flags_signed => Condition.fromCompareOperatorSigned(op), + else => unreachable, + }; + + // mov reg, 0 + // moveq reg, 1 + const zero = Instruction.Operand.imm(0, 0); + const one = Instruction.Operand.imm(1, 0); + writeInt(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, reg, zero).toU32()); + writeInt(u32, try self.code.addManyAsArray(4), Instruction.mov(condition, reg, one).toU32()); + }, .immediate => |x| { if (x > math.maxInt(u32)) return self.fail(src, "ARM registers are 32-bit wide", .{}); diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig index 33ff789648..978c653cb0 100644 --- a/src/codegen/arm.zig +++ b/src/codegen/arm.zig @@ -35,8 +35,74 @@ pub const Condition = enum(u4) { le, /// always al, + + /// Converts a std.math.CompareOperator into a condition flag, + /// i.e. returns the condition that is true iff the result of the + /// comparison is true. Assumes signed comparison + pub fn fromCompareOperatorSigned(op: std.math.CompareOperator) Condition { + return switch (op) { + .gte => .ge, + .gt => .gt, + .neq => .ne, + .lt => .lt, + .lte => .le, + .eq => .eq, + }; + } + + /// Converts a std.math.CompareOperator into a condition flag, + /// i.e. returns the condition that is true iff the result of the + /// comparison is true. Assumes unsigned comparison + pub fn fromCompareOperatorUnsigned(op: std.math.CompareOperator) Condition { + return switch (op) { + .gte => .cs, + .gt => .hi, + .neq => .ne, + .lt => .cc, + .lte => .ls, + .eq => .eq, + }; + } + + /// Returns the condition which is true iff the given condition is + /// false (if such a condition exists) + pub fn negate(cond: Condition) Condition { + return switch (cond) { + .eq => .ne, + .ne => .eq, + .cs => .cc, + .cc => .cs, + .mi => .pl, + .pl => .mi, + .vs => .vc, + .vc => .vs, + .hi => .ls, + .ls => .hi, + .ge => .lt, + .lt => .ge, + .gt => .le, + .le => .gt, + .al => unreachable, + }; + } }; +test "condition from CompareOperator" { + testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorSigned(.eq)); + testing.expectEqual(@as(Condition, .eq), Condition.fromCompareOperatorUnsigned(.eq)); + + testing.expectEqual(@as(Condition, .gt), Condition.fromCompareOperatorSigned(.gt)); + testing.expectEqual(@as(Condition, .hi), Condition.fromCompareOperatorUnsigned(.gt)); + + testing.expectEqual(@as(Condition, .le), Condition.fromCompareOperatorSigned(.lte)); + testing.expectEqual(@as(Condition, .ls), Condition.fromCompareOperatorUnsigned(.lte)); +} + +test "negate condition" { + testing.expectEqual(@as(Condition, .eq), Condition.ne.negate()); + testing.expectEqual(@as(Condition, .ne), Condition.eq.negate()); +} + /// Represents a register in the ARM instruction set architecture pub const Register = enum(u5) { r0, -- cgit v1.2.3 From c5ec096b2ffd42aff6385debe2a412a6db67868f Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sat, 14 Nov 2020 18:25:23 +0100 Subject: stage2 AArch64: add logical (shifted register) instructions --- src/codegen.zig | 23 ++---- src/codegen/aarch64.zig | 200 ++++++++++++++++++++++++++++-------------------- 2 files changed, 125 insertions(+), 98 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen.zig b/src/codegen.zig index 66aa32b14e..9e6de711d4 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2415,19 +2415,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.genSetReg(inst.base.src, reg, arg); } - // TODO move this to lib/std/{elf, macho}.zig, etc. - const is_syscall_inst = switch (self.bin_file.tag) { - .macho => mem.eql(u8, inst.asm_source, "svc #0x80"), - .elf => mem.eql(u8, inst.asm_source, "svc #0"), - else => |tag| return self.fail(inst.base.src, "TODO implement aarch64 support for other syscall instructions for file format: '{}'", .{tag}), - }; - if (is_syscall_inst) { - const imm16: u16 = switch (self.bin_file.tag) { - .macho => 0x80, - .elf => 0, - else => unreachable, - }; - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(imm16).toU32()); + if (mem.eql(u8, inst.asm_source, "svc #0")) { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(0x0).toU32()); + } else if (mem.eql(u8, inst.asm_source, "svc #0x80")) { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(0x80).toU32()); } else { return self.fail(inst.base.src, "TODO implement support for more aarch64 assembly instructions", .{}); } @@ -2876,8 +2867,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // mov r, x0 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr( reg, + .xzr, .x0, - Instruction.RegisterShift.none(), + Instruction.Shift.none, ).toU32()); // ldr x28, [sp], #16 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{ @@ -2908,8 +2900,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // mov r, x0 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr( reg, + .xzr, .x0, - Instruction.RegisterShift.none(), + Instruction.Shift.none, ).toU32()); // ldp x0, x28, [sp, #16] mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldp( diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 0e9ad61745..50cdf6a262 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -200,17 +200,6 @@ test "FloatingPointRegister.toX" { /// Represents an instruction in the AArch64 instruction set pub const Instruction = union(enum) { - OrShiftedRegister: packed struct { - rd: u5, - rn: u5, - imm6: u6, - rm: u5, - n: u1, - shift: u2, - fixed: u5 = 0b01010, - opc: u2 = 0b01, - sf: u1, - }, MoveWideImmediate: packed struct { rd: u5, imm16: u16, @@ -274,10 +263,37 @@ pub const Instruction = union(enum) { NoOperation: packed struct { fixed: u32 = 0b1101010100_0_00_011_0010_0000_000_11111, }, + LogicalShiftedRegister: packed struct { + rd: u5, + rn: u5, + imm6: u6, + rm: u5, + n: u1, + shift: u2, + fixed: u5 = 0b01010, + opc: u2, + sf: u1, + }, + + pub const Shift = struct { + shift: Type = .lsl, + amount: u6 = 0, + + pub const Type = enum(u2) { + lsl, + lsr, + asr, + ror, + }; + + pub const none = Shift{ + .shift = .lsl, + .amount = 0, + }; + }; pub fn toU32(self: Instruction) u32 { return switch (self) { - .OrShiftedRegister => |v| @bitCast(u32, v), .MoveWideImmediate => |v| @bitCast(u32, v), .PCRelativeAddress => |v| @bitCast(u32, v), .LoadStoreRegister => |v| @bitCast(u32, v), @@ -287,68 +303,10 @@ pub const Instruction = union(enum) { .UnconditionalBranchRegister => |v| @bitCast(u32, v), .UnconditionalBranchImmediate => |v| @bitCast(u32, v), .NoOperation => |v| @bitCast(u32, v), + .LogicalShiftedRegister => |v| @bitCast(u32, v), }; } - pub const RegisterShift = struct { - rn: u5, - imm6: u6, - shift: enum(u2) { - Lsl = 0, - Lsr = 1, - Asr = 2, - Ror = 3, - }, - - pub fn none() RegisterShift { - return .{ - .rn = 0b11111, - .imm6 = 0, - .shift = .Lsl, - }; - } - }; - - // Helper functions for assembly syntax functions - - fn orShiftedRegister( - rd: Register, - rm: Register, - shift: RegisterShift, - invert: bool, - ) Instruction { - const n: u1 = if (invert) 1 else 0; - switch (rd.size()) { - 32 => { - return Instruction{ - .OrShiftedRegister = .{ - .rd = rd.id(), - .rn = shift.rn, - .imm6 = shift.imm6, - .rm = rm.id(), - .n = n, - .shift = @enumToInt(shift.shift), - .sf = 0, - }, - }; - }, - 64 => { - return Instruction{ - .OrShiftedRegister = .{ - .rd = rd.id(), - .rn = shift.rn, - .imm6 = shift.imm6, - .rm = rm.id(), - .n = n, - .shift = @enumToInt(shift.shift), - .sf = 1, - }, - }; - }, - else => unreachable, // unexpected register size - } - } - fn moveWideImmediate( opc: u2, rd: Register, @@ -671,15 +629,49 @@ pub const Instruction = union(enum) { }; } - // Bitwise (inclusive) OR of a register value - - pub fn orr(rd: Register, rm: Register, shift: RegisterShift) Instruction { - return orShiftedRegister(rd, rm, shift, false); + fn logicalShiftedRegister( + opc: u2, + n: u1, + shift: Shift, + rd: Register, + rn: Register, + rm: Register, + ) Instruction { + switch (rd.size()) { + 32 => { + assert(shift.amount < 32); + return Instruction{ + .LogicalShiftedRegister = .{ + .rd = rd.id(), + .rn = rn.id(), + .imm6 = shift.amount, + .rm = rm.id(), + .n = n, + .shift = @enumToInt(shift.shift), + .opc = opc, + .sf = 0b0, + }, + }; + }, + 64 => { + return Instruction{ + .LogicalShiftedRegister = .{ + .rd = rd.id(), + .rn = rn.id(), + .imm6 = shift.amount, + .rm = rm.id(), + .n = n, + .shift = @enumToInt(shift.shift), + .opc = opc, + .sf = 0b1, + }, + }; + }, + else => unreachable, // unexpected register size + } } - pub fn orn(rd: Register, rm: Register, shift: RegisterShift) Instruction { - return orShiftedRegister(rd, rm, shift, true); - } + // Helper functions for assembly syntax functions // Move wide (immediate) @@ -823,6 +815,40 @@ pub const Instruction = union(enum) { pub fn nop() Instruction { return Instruction{ .NoOperation = {} }; } + + // Logical (shifted register) + + pub fn @"and"(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b00, 0b0, shift, rd, rn, rm); + } + + pub fn bic(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b00, 0b1, shift, rd, rn, rm); + } + + pub fn orr(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b01, 0b0, shift, rd, rn, rm); + } + + pub fn orn(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b01, 0b1, shift, rd, rn, rm); + } + + pub fn eor(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b10, 0b0, shift, rd, rn, rm); + } + + pub fn eon(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b10, 0b1, shift, rd, rn, rm); + } + + pub fn ands(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b11, 0b0, shift, rd, rn, rm); + } + + pub fn bics(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { + return logicalShiftedRegister(0b11, 0b1, shift, rd, rn, rm); + } }; test "" { @@ -836,15 +862,15 @@ test "serialize instructions" { }; const testcases = [_]Testcase{ - .{ // orr x0 x1 - .inst = Instruction.orr(.x0, .x1, Instruction.RegisterShift.none()), + .{ // orr x0, xzr, x1 + .inst = Instruction.orr(.x0, .xzr, .x1, Instruction.Shift.none), .expected = 0b1_01_01010_00_0_00001_000000_11111_00000, }, - .{ // orn x0 x1 - .inst = Instruction.orn(.x0, .x1, Instruction.RegisterShift.none()), + .{ // orn x0, xzr, x1 + .inst = Instruction.orn(.x0, .xzr, .x1, Instruction.Shift.none), .expected = 0b1_01_01010_00_1_00001_000000_11111_00000, }, - .{ // movz x1 #4 + .{ // movz x1, #4 .inst = Instruction.movz(.x1, 4, 0), .expected = 0b1_10_100101_00_0000000000000100_00001, }, @@ -944,6 +970,14 @@ test "serialize instructions" { .inst = Instruction.ldp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.post_index(16)), .expected = 0b10_101_0_001_1_0000010_00010_11111_00001, }, + .{ // and x0, x4, x2 + .inst = Instruction.@"and"(.x0, .x4, .x2, .{}), + .expected = 0b1_00_01010_00_0_00010_000000_00100_00000, + }, + .{ // and x0, x4, x2, lsl #0x8 + .inst = Instruction.@"and"(.x0, .x4, .x2, .{ .shift = .lsl, .amount = 0x8 }), + .expected = 0b1_00_01010_00_0_00010_001000_00100_00000, + }, }; for (testcases) |case| { -- cgit v1.2.3 From 1c13ca5a05978011283ff55a586443b10b69fc85 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 26 Nov 2020 13:19:30 +0100 Subject: stage2: Use {s} instead of {} when formatting strings --- lib/std/meta/trait.zig | 14 +++ src/Cache.zig | 4 +- src/Compilation.zig | 94 +++++++++--------- src/DepTokenizer.zig | 6 +- src/Module.zig | 16 ++-- src/astgen.zig | 8 +- src/codegen.zig | 38 ++++---- src/codegen/c.zig | 15 +-- src/codegen/llvm.zig | 125 ++++++++++++++++++++++++ src/glibc.zig | 26 ++--- src/libc_installation.zig | 32 +++---- src/link.zig | 8 +- src/link/C.zig | 5 +- src/link/Coff.zig | 10 +- src/link/Elf.zig | 18 ++-- src/link/MachO.zig | 22 ++--- src/link/Wasm.zig | 6 +- src/main.zig | 236 +++++++++++++++++++++++----------------------- src/mingw.zig | 4 +- src/musl.zig | 8 +- src/print_env.zig | 2 +- src/print_targets.zig | 4 +- src/stage1.zig | 4 +- src/translate_c.zig | 76 +++++++-------- src/value.zig | 4 +- src/zir.zig | 44 ++++----- src/zir_sema.zig | 34 +++---- 27 files changed, 503 insertions(+), 360 deletions(-) create mode 100644 src/codegen/llvm.zig (limited to 'src/codegen') diff --git a/lib/std/meta/trait.zig b/lib/std/meta/trait.zig index 8c8b26cf45..8e54293533 100644 --- a/lib/std/meta/trait.zig +++ b/lib/std/meta/trait.zig @@ -298,6 +298,20 @@ pub fn isNumber(comptime T: type) bool { }; } +pub fn isIntegerNumber(comptime T: type) bool { + return switch (@typeInfo(T)) { + .Int, .ComptimeInt => true, + else => false, + }; +} + +pub fn isFloatingNumber(comptime T: type) bool { + return switch (@typeInfo(T)) { + .Float, .ComptimeFloat => true, + else => false, + }; +} + test "std.meta.trait.isNumber" { const NotANumber = struct { number: u8, diff --git a/src/Cache.zig b/src/Cache.zig index 3d33226f27..03a0d61157 100644 --- a/src/Cache.zig +++ b/src/Cache.zig @@ -549,7 +549,7 @@ pub const Manifest = struct { .target, .target_must_resolve, .prereq => {}, else => |err| { try err.printError(error_buf.writer()); - std.log.err("failed parsing {}: {}", .{ dep_file_basename, error_buf.items }); + std.log.err("failed parsing {s}: {s}", .{ dep_file_basename, error_buf.items }); return error.InvalidDepFile; }, } @@ -561,7 +561,7 @@ pub const Manifest = struct { .prereq => |bytes| try self.addFilePost(bytes), else => |err| { try err.printError(error_buf.writer()); - std.log.err("failed parsing {}: {}", .{ dep_file_basename, error_buf.items }); + std.log.err("failed parsing {s}: {s}", .{ dep_file_basename, error_buf.items }); return error.InvalidDepFile; }, } diff --git a/src/Compilation.zig b/src/Compilation.zig index 33376d46d2..3a9c1ea993 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1475,7 +1475,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor // lifetime annotations in the ZIR. var decl_arena = decl.typed_value.most_recent.arena.?.promote(module.gpa); defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; - log.debug("analyze liveness of {}\n", .{decl.name}); + log.debug("analyze liveness of {s}\n", .{decl.name}); try liveness.analyze(module.gpa, &decl_arena.allocator, func.analysis.success); } @@ -1492,7 +1492,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( module.gpa, decl.src(), - "unable to codegen: {}", + "unable to codegen: {s}", .{@errorName(err)}, )); decl.analysis = .codegen_failure_retryable; @@ -1535,7 +1535,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( module.gpa, decl.src(), - "unable to update line number: {}", + "unable to update line number: {s}", .{@errorName(err)}, )); decl.analysis = .codegen_failure_retryable; @@ -1544,50 +1544,50 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .glibc_crt_file => |crt_file| { glibc.buildCRTFile(self, crt_file) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build glibc CRT file: {}", .{@errorName(err)}); + fatal("unable to build glibc CRT file: {s}", .{@errorName(err)}); }; }, .glibc_shared_objects => { glibc.buildSharedObjects(self) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build glibc shared objects: {}", .{@errorName(err)}); + fatal("unable to build glibc shared objects: {s}", .{@errorName(err)}); }; }, .musl_crt_file => |crt_file| { musl.buildCRTFile(self, crt_file) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build musl CRT file: {}", .{@errorName(err)}); + fatal("unable to build musl CRT file: {s}", .{@errorName(err)}); }; }, .mingw_crt_file => |crt_file| { mingw.buildCRTFile(self, crt_file) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build mingw-w64 CRT file: {}", .{@errorName(err)}); + fatal("unable to build mingw-w64 CRT file: {s}", .{@errorName(err)}); }; }, .windows_import_lib => |index| { const link_lib = self.bin_file.options.system_libs.items()[index].key; mingw.buildImportLib(self, link_lib) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to generate DLL import .lib file: {}", .{@errorName(err)}); + fatal("unable to generate DLL import .lib file: {s}", .{@errorName(err)}); }; }, .libunwind => { libunwind.buildStaticLib(self) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build libunwind: {}", .{@errorName(err)}); + fatal("unable to build libunwind: {s}", .{@errorName(err)}); }; }, .libcxx => { libcxx.buildLibCXX(self) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build libcxx: {}", .{@errorName(err)}); + fatal("unable to build libcxx: {s}", .{@errorName(err)}); }; }, .libcxxabi => { libcxx.buildLibCXXABI(self) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build libcxxabi: {}", .{@errorName(err)}); + fatal("unable to build libcxxabi: {s}", .{@errorName(err)}); }; }, .libtsan => { @@ -1611,20 +1611,20 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .libssp => { self.buildOutputFromZig("ssp.zig", .Lib, &self.libssp_static_lib) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build libssp: {}", .{@errorName(err)}); + fatal("unable to build libssp: {s}", .{@errorName(err)}); }; }, .zig_libc => { self.buildOutputFromZig("c.zig", .Lib, &self.libc_static_lib) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build zig's multitarget libc: {}", .{@errorName(err)}); + fatal("unable to build zig's multitarget libc: {s}", .{@errorName(err)}); }; }, .generate_builtin_zig => { // This Job is only queued up if there is a zig module. self.updateBuiltinZigFile(self.bin_file.options.module.?) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to update builtin.zig file: {}", .{@errorName(err)}); + fatal("unable to update builtin.zig file: {s}", .{@errorName(err)}); }; }, .stage1_module => { @@ -1704,11 +1704,11 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { const out_h_path = try comp.local_cache_directory.join(arena, &[_][]const u8{ tmp_dir_sub_path, cimport_basename, }); - const out_dep_path = try std.fmt.allocPrint(arena, "{}.d", .{out_h_path}); + const out_dep_path = try std.fmt.allocPrint(arena, "{s}.d", .{out_h_path}); try zig_cache_tmp_dir.writeFile(cimport_basename, c_src); if (comp.verbose_cimport) { - log.info("C import source: {}", .{out_h_path}); + log.info("C import source: {s}", .{out_h_path}); } var argv = std.ArrayList([]const u8).init(comp.gpa); @@ -1755,7 +1755,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { defer tree.deinit(); if (comp.verbose_cimport) { - log.info("C import .d file: {}", .{out_dep_path}); + log.info("C import .d file: {s}", .{out_dep_path}); } const dep_basename = std.fs.path.basename(out_dep_path); @@ -1775,7 +1775,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { try bos.flush(); man.writeManifest() catch |err| { - log.warn("failed to write cache manifest for C import: {}", .{@errorName(err)}); + log.warn("failed to write cache manifest for C import: {s}", .{@errorName(err)}); }; break :digest digest; @@ -1785,7 +1785,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { "o", &digest, cimport_zig_basename, }); if (comp.verbose_cimport) { - log.info("C import output: {}\n", .{out_zig_path}); + log.info("C import output: {s}\n", .{out_zig_path}); } return CImportResult{ .out_zig_path = out_zig_path, @@ -1946,7 +1946,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * child.stderr_behavior = .Inherit; const term = child.spawnAndWait() catch |err| { - return comp.failCObj(c_object, "unable to spawn {}: {}", .{ argv.items[0], @errorName(err) }); + return comp.failCObj(c_object, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); }; switch (term) { .Exited => |code| { @@ -1974,7 +1974,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024); const term = child.wait() catch |err| { - return comp.failCObj(c_object, "unable to spawn {}: {}", .{ argv.items[0], @errorName(err) }); + return comp.failCObj(c_object, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); }; switch (term) { @@ -1982,12 +1982,12 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * if (code != 0) { // TODO parse clang stderr and turn it into an error message // and then call failCObjWithOwnedErrorMsg - log.err("clang failed with stderr: {}", .{stderr}); + log.err("clang failed with stderr: {s}", .{stderr}); return comp.failCObj(c_object, "clang exited with code {}", .{code}); } }, else => { - log.err("clang terminated with stderr: {}", .{stderr}); + log.err("clang terminated with stderr: {s}", .{stderr}); return comp.failCObj(c_object, "clang terminated unexpectedly", .{}); }, } @@ -1999,7 +1999,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * try man.addDepFilePost(zig_cache_tmp_dir, dep_basename); // Just to save disk space, we delete the file because it is never needed again. zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| { - log.warn("failed to delete '{}': {}", .{ dep_file_path, @errorName(err) }); + log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }); }; } @@ -2015,7 +2015,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * try std.fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename); man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when compiling '{}': {}", .{ c_object.src.src_path, @errorName(err) }); + log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ c_object.src.src_path, @errorName(err) }); }; break :blk digest; }; @@ -2034,7 +2034,7 @@ pub fn tmpFilePath(comp: *Compilation, arena: *Allocator, suffix: []const u8) er const s = std.fs.path.sep_str; const rand_int = std.crypto.random.int(u64); if (comp.local_cache_directory.path) |p| { - return std.fmt.allocPrint(arena, "{}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); + return std.fmt.allocPrint(arena, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); } else { return std.fmt.allocPrint(arena, "tmp" ++ s ++ "{x}-{s}", .{ rand_int, suffix }); } @@ -2144,7 +2144,7 @@ pub fn addCCArgs( } const mcmodel = comp.bin_file.options.machine_code_model; if (mcmodel != .default) { - try argv.append(try std.fmt.allocPrint(arena, "-mcmodel={}", .{@tagName(mcmodel)})); + try argv.append(try std.fmt.allocPrint(arena, "-mcmodel={s}", .{@tagName(mcmodel)})); } switch (target.os.tag) { @@ -2497,22 +2497,22 @@ fn detectLibCIncludeDirs( const s = std.fs.path.sep_str; const arch_include_dir = try std.fmt.allocPrint( arena, - "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-{}", + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-{s}", .{ zig_lib_dir, arch_name, os_name, abi_name }, ); const generic_include_dir = try std.fmt.allocPrint( arena, - "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "generic-{}", + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "generic-{s}", .{ zig_lib_dir, generic_name }, ); const arch_os_include_dir = try std.fmt.allocPrint( arena, - "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-any", + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-any", .{ zig_lib_dir, @tagName(target.cpu.arch), os_name }, ); const generic_os_include_dir = try std.fmt.allocPrint( arena, - "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{}-any", + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{s}-any", .{ zig_lib_dir, os_name }, ); @@ -2631,9 +2631,9 @@ fn updateBuiltinZigFile(comp: *Compilation, mod: *Module) !void { pub fn dump_argv(argv: []const []const u8) void { for (argv[0 .. argv.len - 1]) |arg| { - std.debug.print("{} ", .{arg}); + std.debug.print("{s} ", .{arg}); } - std.debug.print("{}\n", .{argv[argv.len - 1]}); + std.debug.print("{s}\n", .{argv[argv.len - 1]}); } pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 { @@ -2653,15 +2653,15 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\pub const arch = Target.current.cpu.arch; \\/// Deprecated \\pub const endian = Target.current.cpu.arch.endian(); - \\pub const output_mode = OutputMode.{}; - \\pub const link_mode = LinkMode.{}; + \\pub const output_mode = OutputMode.{s}; + \\pub const link_mode = LinkMode.{s}; \\pub const is_test = {}; \\pub const single_threaded = {}; - \\pub const abi = Abi.{}; + \\pub const abi = Abi.{s}; \\pub const cpu: Cpu = Cpu{{ - \\ .arch = .{}, - \\ .model = &Target.{}.cpu.{}, - \\ .features = Target.{}.featureSet(&[_]Target.{}.Feature{{ + \\ .arch = .{s}, + \\ .model = &Target.{s}.cpu.{s}, + \\ .features = Target.{s}.featureSet(&[_]Target.{s}.Feature{{ \\ , .{ @tagName(comp.bin_file.options.output_mode), @@ -2692,7 +2692,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\ }}), \\}}; \\pub const os = Os{{ - \\ .tag = .{}, + \\ .tag = .{s}, \\ .version_range = .{{ , .{@tagName(target.os.tag)}, @@ -2778,8 +2778,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 (comp.bin_file.options.skip_linker_dependencies and comp.bin_file.options.parent_compilation_link_libc); try buffer.writer().print( - \\pub const object_format = ObjectFormat.{}; - \\pub const mode = Mode.{}; + \\pub const object_format = ObjectFormat.{s}; + \\pub const mode = Mode.{s}; \\pub const link_libc = {}; \\pub const link_libcpp = {}; \\pub const have_error_return_tracing = {}; @@ -2787,7 +2787,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\pub const position_independent_code = {}; \\pub const position_independent_executable = {}; \\pub const strip_debug_info = {}; - \\pub const code_model = CodeModel.{}; + \\pub const code_model = CodeModel.{s}; \\ , .{ @tagName(comp.bin_file.options.object_format), @@ -3013,7 +3013,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("stage1 {} new_digest={} error: {}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); + log.debug("stage1 {} new_digest={} error: {s}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -3189,7 +3189,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node // Update the small file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. const stage1_flags_byte = @bitCast(u8, mod.stage1_flags); - log.debug("stage1 {} final digest={} flags={x}", .{ + log.debug("stage1 {s} final digest={} flags={x}", .{ mod.root_pkg.root_src_path, digest, stage1_flags_byte, }); var digest_plus_flags: [digest.len + 2]u8 = undefined; @@ -3202,11 +3202,11 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node digest_plus_flags, stage1_flags_byte, mod.stage1_flags.have_winmain_crt_startup, }); Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest_plus_flags) catch |err| { - log.warn("failed to save stage1 hash digest file: {}", .{@errorName(err)}); + log.warn("failed to save stage1 hash digest file: {s}", .{@errorName(err)}); }; // Failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)}); + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); }; // We hang on to this lock so that the output file path can be used without // other processes clobbering it. diff --git a/src/DepTokenizer.zig b/src/DepTokenizer.zig index 99db6e4b3c..c42583a786 100644 --- a/src/DepTokenizer.zig +++ b/src/DepTokenizer.zig @@ -366,7 +366,7 @@ pub const Token = union(enum) { .incomplete_quoted_prerequisite, .incomplete_target, => |index_and_bytes| { - try writer.print("{} '", .{self.errStr()}); + try writer.print("{s} '", .{self.errStr()}); if (self == .incomplete_target) { const tmp = Token{ .target_must_resolve = index_and_bytes.bytes }; try tmp.resolve(writer); @@ -383,7 +383,7 @@ pub const Token = union(enum) { => |index_and_char| { try writer.writeAll("illegal char "); try printUnderstandableChar(writer, index_and_char.char); - try writer.print(" at position {}: {}", .{ index_and_char.index, self.errStr() }); + try writer.print(" at position {}: {s}", .{ index_and_char.index, self.errStr() }); }, } } @@ -943,7 +943,7 @@ fn printSection(out: anytype, label: []const u8, bytes: []const u8) !void { fn printLabel(out: anytype, label: []const u8, bytes: []const u8) !void { var buf: [80]u8 = undefined; - var text = try std.fmt.bufPrint(buf[0..], "{} {} bytes ", .{ label, bytes.len }); + var text = try std.fmt.bufPrint(buf[0..], "{s} {} bytes ", .{ label, bytes.len }); try out.writeAll(text); var i: usize = text.len; const end = 79; diff --git a/src/Module.zig b/src/Module.zig index 5ea78d06d1..e0042d6c4d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -953,7 +953,7 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { self.failed_decls.putAssumeCapacityNoClobber(decl, try Compilation.ErrorMsg.create( self.gpa, decl.src(), - "unable to analyze: {}", + "unable to analyze: {s}", .{@errorName(err)}, )); decl.analysis = .sema_failure_retryable; @@ -1475,7 +1475,7 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { if (zir_module.error_msg) |src_err_msg| { self.failed_files.putAssumeCapacityNoClobber( &root_scope.base, - try Compilation.ErrorMsg.create(self.gpa, src_err_msg.byte_offset, "{}", .{src_err_msg.msg}), + try Compilation.ErrorMsg.create(self.gpa, src_err_msg.byte_offset, "{s}", .{src_err_msg.msg}), ); root_scope.status = .unloaded_parse_failure; return error.AnalysisFail; @@ -1581,7 +1581,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.src_index = decl_i; if (deleted_decls.remove(decl) == null) { decl.analysis = .sema_failure; - const err_msg = try Compilation.ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); + const err_msg = try Compilation.ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{s}'", .{decl.name}); errdefer err_msg.destroy(self.gpa); try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); } else { @@ -1623,7 +1623,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.src_index = decl_i; if (deleted_decls.remove(decl) == null) { decl.analysis = .sema_failure; - const err_msg = try Compilation.ErrorMsg.create(self.gpa, name_loc.start, "redefinition of '{}'", .{decl.name}); + const err_msg = try Compilation.ErrorMsg.create(self.gpa, name_loc.start, "redefinition of '{s}'", .{decl.name}); errdefer err_msg.destroy(self.gpa); try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); } else if (!srcHashEql(decl.contents_hash, contents_hash)) { @@ -1991,7 +1991,7 @@ pub fn analyzeExport( self.failed_exports.putAssumeCapacityNoClobber(new_export, try Compilation.ErrorMsg.create( self.gpa, src, - "exported symbol collision: {}", + "exported symbol collision: {s}", .{symbol_name}, )); // TODO: add a note @@ -2007,7 +2007,7 @@ pub fn analyzeExport( self.failed_exports.putAssumeCapacityNoClobber(new_export, try Compilation.ErrorMsg.create( self.gpa, src, - "unable to export: {}", + "unable to export: {s}", .{@errorName(err)}, )); new_export.status = .failed_retryable; @@ -2277,7 +2277,7 @@ pub fn createAnonymousDecl( ) !*Decl { const name_index = self.getNextAnonNameIndex(); const scope_decl = scope.decl().?; - const name = try std.fmt.allocPrint(self.gpa, "{}__anon_{}", .{ scope_decl.name, name_index }); + const name = try std.fmt.allocPrint(self.gpa, "{s}__anon_{}", .{ scope_decl.name, name_index }); defer self.gpa.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); const src_hash: std.zig.SrcHash = undefined; @@ -2384,7 +2384,7 @@ pub fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_sr pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst { const decl = self.lookupDeclName(scope, decl_name) orelse - return self.fail(scope, src, "decl '{}' not found", .{decl_name}); + return self.fail(scope, src, "decl '{s}' not found", .{decl_name}); return self.analyzeDeclRef(scope, src, decl); } diff --git a/src/astgen.zig b/src/astgen.zig index 3670cb260e..b4e1760368 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1955,7 +1955,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo error.Overflow => return mod.failNode( scope, &ident.base, - "primitive integer type '{}' exceeds maximum bit width of 65535", + "primitive integer type '{s}' exceeds maximum bit width of 65535", .{ident_name}, ), error.InvalidCharacter => break :integer, @@ -2010,7 +2010,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{})); } - return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name}); + return mod.failNode(scope, &ident.base, "use of undeclared identifier '{s}'", .{ident_name}); } fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.OneToken) InnerError!*zir.Inst { @@ -2204,7 +2204,7 @@ fn ensureBuiltinParamCount(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinC return; const s = if (count == 1) "" else "s"; - return mod.failTok(scope, call.builtin_token, "expected {} parameter{}, found {}", .{ count, s, call.params_len }); + return mod.failTok(scope, call.builtin_token, "expected {} parameter{s}, found {}", .{ count, s, call.params_len }); } fn simpleCast( @@ -2383,7 +2383,7 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built } else if (mem.eql(u8, builtin_name, "@compileError")) { return compileError(mod, scope, call); } else { - return mod.failTok(scope, call.builtin_token, "invalid builtin function: '{}'", .{builtin_name}); + return mod.failTok(scope, call.builtin_token, "invalid builtin function: '{s}'", .{builtin_name}); } } diff --git a/src/codegen.zig b/src/codegen.zig index 9e6de711d4..6530b687e5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -228,7 +228,7 @@ pub fn generateSymbol( .fail = try ErrorMsg.create( bin_file.allocator, src, - "TODO implement generateSymbol for type '{}'", + "TODO implement generateSymbol for type '{s}'", .{@tagName(t)}, ), }; @@ -2029,7 +2029,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }); break :blk 0x84; }, - else => return self.fail(inst.base.src, "TODO implement condbr {} when condition is {}", .{ self.target.cpu.arch, @tagName(cond) }), + else => return self.fail(inst.base.src, "TODO implement condbr {s} when condition is {s}", .{ self.target.cpu.arch, @tagName(cond) }), }; self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode }); const reloc = Reloc{ .rel32 = self.code.items.len }; @@ -2376,11 +2376,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .arm, .armeb => { for (inst.inputs) |input, i| { if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { - return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); + return self.fail(inst.base.src, "unrecognized asm input constraint: '{s}'", .{input}); } const reg_name = input[1 .. input.len - 1]; const reg = parseRegName(reg_name) orelse - return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); const arg = try self.resolveInst(inst.args[i]); try self.genSetReg(inst.base.src, reg, arg); } @@ -2393,11 +2393,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (inst.output) |output| { if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { - return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); + return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output}); } const reg_name = output[2 .. output.len - 1]; const reg = parseRegName(reg_name) orelse - return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); return MCValue{ .register = reg }; } else { return MCValue.none; @@ -2406,11 +2406,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .aarch64 => { for (inst.inputs) |input, i| { if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { - return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); + return self.fail(inst.base.src, "unrecognized asm input constraint: '{s}'", .{input}); } const reg_name = input[1 .. input.len - 1]; const reg = parseRegName(reg_name) orelse - return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); const arg = try self.resolveInst(inst.args[i]); try self.genSetReg(inst.base.src, reg, arg); } @@ -2425,11 +2425,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (inst.output) |output| { if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { - return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); + return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output}); } const reg_name = output[2 .. output.len - 1]; const reg = parseRegName(reg_name) orelse - return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); return MCValue{ .register = reg }; } else { return MCValue.none; @@ -2438,11 +2438,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .riscv64 => { for (inst.inputs) |input, i| { if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { - return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); + return self.fail(inst.base.src, "unrecognized asm input constraint: '{s}'", .{input}); } const reg_name = input[1 .. input.len - 1]; const reg = parseRegName(reg_name) orelse - return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); const arg = try self.resolveInst(inst.args[i]); try self.genSetReg(inst.base.src, reg, arg); } @@ -2455,11 +2455,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (inst.output) |output| { if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { - return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); + return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output}); } const reg_name = output[2 .. output.len - 1]; const reg = parseRegName(reg_name) orelse - return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); return MCValue{ .register = reg }; } else { return MCValue.none; @@ -2468,11 +2468,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .x86_64, .i386 => { for (inst.inputs) |input, i| { if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { - return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); + return self.fail(inst.base.src, "unrecognized asm input constraint: '{s}'", .{input}); } const reg_name = input[1 .. input.len - 1]; const reg = parseRegName(reg_name) orelse - return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); const arg = try self.resolveInst(inst.args[i]); try self.genSetReg(inst.base.src, reg, arg); } @@ -2485,11 +2485,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (inst.output) |output| { if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { - return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); + return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output}); } const reg_name = output[2 .. output.len - 1]; const reg = parseRegName(reg_name) orelse - return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return self.fail(inst.base.src, "unrecognized register: '{s}'", .{reg_name}); return MCValue{ .register = reg }; } else { return MCValue.none; @@ -3417,7 +3417,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { next_int_reg += 1; } }, - else => return self.fail(src, "TODO implement function parameters of type {}", .{@tagName(ty.zigTypeTag())}), + else => return self.fail(src, "TODO implement function parameters of type {s}", .{@tagName(ty.zigTypeTag())}), } } result.stack_byte_count = next_stack_offset; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index c6c29942d9..b97e1590e3 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -235,7 +235,7 @@ fn renderFunctionSignature( try writer.writeAll(", "); } try renderType(ctx, writer, tv.ty.fnParamType(index)); - try writer.print(" arg{}", .{index}); + try writer.print(" arg{d}", .{index}); } } try writer.writeByte(')'); @@ -481,8 +481,9 @@ fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !? const rhs = try ctx.resolveInst(inst.rhs); const writer = file.main.writer(); const name = try ctx.name(); - try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); - try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs }); + try writer.writeAll(indentation ++ "const "); + try renderType(ctx, writer, inst.base.ty); + try writer.print(" {s} = {s} " ++ operator ++ " {s};\n", .{ name, lhs, rhs }); return name; } @@ -587,7 +588,7 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { const arg = as.args[index]; try writer.writeAll("register "); try renderType(ctx, writer, arg.ty); - try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg }); + try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); // TODO merge constant handling into inst_map as well if (arg.castTag(.constant)) |c| { try renderValue(ctx, writer, arg.ty, c.val); @@ -597,13 +598,13 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { if (!gop.found_existing) { return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); } - try writer.print("{};\n ", .{gop.entry.value}); + try writer.print("{s};\n ", .{gop.entry.value}); } } else { return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); } } - try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); + try writer.print("__asm {s} (\"{s}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); if (as.output) |o| { return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{}); } @@ -619,7 +620,7 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { if (index > 0) { try writer.writeAll(", "); } - try writer.print("\"\"({}_constant)", .{reg}); + try writer.print("\"\"({s}_constant)", .{reg}); } else { // This is blocked by the earlier test unreachable; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig new file mode 100644 index 0000000000..3a1ebada3b --- /dev/null +++ b/src/codegen/llvm.zig @@ -0,0 +1,125 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 { + const llvm_arch = switch (target.cpu.arch) { + .arm => "arm", + .armeb => "armeb", + .aarch64 => "aarch64", + .aarch64_be => "aarch64_be", + .aarch64_32 => "aarch64_32", + .arc => "arc", + .avr => "avr", + .bpfel => "bpfel", + .bpfeb => "bpfeb", + .hexagon => "hexagon", + .mips => "mips", + .mipsel => "mipsel", + .mips64 => "mips64", + .mips64el => "mips64el", + .msp430 => "msp430", + .powerpc => "powerpc", + .powerpc64 => "powerpc64", + .powerpc64le => "powerpc64le", + .r600 => "r600", + .amdgcn => "amdgcn", + .riscv32 => "riscv32", + .riscv64 => "riscv64", + .sparc => "sparc", + .sparcv9 => "sparcv9", + .sparcel => "sparcel", + .s390x => "s390x", + .tce => "tce", + .tcele => "tcele", + .thumb => "thumb", + .thumbeb => "thumbeb", + .i386 => "i386", + .x86_64 => "x86_64", + .xcore => "xcore", + .nvptx => "nvptx", + .nvptx64 => "nvptx64", + .le32 => "le32", + .le64 => "le64", + .amdil => "amdil", + .amdil64 => "amdil64", + .hsail => "hsail", + .hsail64 => "hsail64", + .spir => "spir", + .spir64 => "spir64", + .kalimba => "kalimba", + .shave => "shave", + .lanai => "lanai", + .wasm32 => "wasm32", + .wasm64 => "wasm64", + .renderscript32 => "renderscript32", + .renderscript64 => "renderscript64", + .ve => "ve", + .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, + }; + // TODO Add a sub-arch for some architectures depending on CPU features. + + const llvm_os = switch (target.os.tag) { + .freestanding => "unknown", + .ananas => "ananas", + .cloudabi => "cloudabi", + .dragonfly => "dragonfly", + .freebsd => "freebsd", + .fuchsia => "fuchsia", + .ios => "ios", + .kfreebsd => "kfreebsd", + .linux => "linux", + .lv2 => "lv2", + .macos => "macosx", + .netbsd => "netbsd", + .openbsd => "openbsd", + .solaris => "solaris", + .windows => "windows", + .haiku => "haiku", + .minix => "minix", + .rtems => "rtems", + .nacl => "nacl", + .cnk => "cnk", + .aix => "aix", + .cuda => "cuda", + .nvcl => "nvcl", + .amdhsa => "amdhsa", + .ps4 => "ps4", + .elfiamcu => "elfiamcu", + .tvos => "tvos", + .watchos => "watchos", + .mesa3d => "mesa3d", + .contiki => "contiki", + .amdpal => "amdpal", + .hermit => "hermit", + .hurd => "hurd", + .wasi => "wasi", + .emscripten => "emscripten", + .uefi => "windows", + .other => "unknown", + }; + + const llvm_abi = switch (target.abi) { + .none => "unknown", + .gnu => "gnu", + .gnuabin32 => "gnuabin32", + .gnuabi64 => "gnuabi64", + .gnueabi => "gnueabi", + .gnueabihf => "gnueabihf", + .gnux32 => "gnux32", + .code16 => "code16", + .eabi => "eabi", + .eabihf => "eabihf", + .android => "android", + .musl => "musl", + .musleabi => "musleabi", + .musleabihf => "musleabihf", + .msvc => "msvc", + .itanium => "itanium", + .cygnus => "cygnus", + .coreclr => "coreclr", + .simulator => "simulator", + .macabi => "macabi", + }; + + return std.fmt.allocPrint(allocator, "{s}-unknown-{s}-{s}", .{ llvm_arch, llvm_os, llvm_abi }); +} diff --git a/src/glibc.zig b/src/glibc.zig index 0c27872720..f69dd11ada 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -72,7 +72,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! errdefer version_table.deinit(gpa); var glibc_dir = zig_lib_dir.openDir("libc" ++ path.sep_str ++ "glibc", .{}) catch |err| { - std.log.err("unable to open glibc dir: {}", .{@errorName(err)}); + std.log.err("unable to open glibc dir: {s}", .{@errorName(err)}); return error.ZigInstallationCorrupt; }; defer glibc_dir.close(); @@ -81,7 +81,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! const vers_txt_contents = glibc_dir.readFileAlloc(gpa, "vers.txt", max_txt_size) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { - std.log.err("unable to read vers.txt: {}", .{@errorName(err)}); + std.log.err("unable to read vers.txt: {s}", .{@errorName(err)}); return error.ZigInstallationCorrupt; }, }; @@ -91,7 +91,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! const fns_txt_contents = glibc_dir.readFileAlloc(arena, "fns.txt", max_txt_size) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { - std.log.err("unable to read fns.txt: {}", .{@errorName(err)}); + std.log.err("unable to read fns.txt: {s}", .{@errorName(err)}); return error.ZigInstallationCorrupt; }, }; @@ -99,7 +99,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! const abi_txt_contents = glibc_dir.readFileAlloc(gpa, "abi.txt", max_txt_size) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { - std.log.err("unable to read abi.txt: {}", .{@errorName(err)}); + std.log.err("unable to read abi.txt: {s}", .{@errorName(err)}); return error.ZigInstallationCorrupt; }, }; @@ -116,7 +116,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! } const adjusted_line = line[prefix.len..]; const ver = std.builtin.Version.parse(adjusted_line) catch |err| { - std.log.err("vers.txt:{}: unable to parse glibc version '{}': {}", .{ line_i, line, @errorName(err) }); + std.log.err("vers.txt:{}: unable to parse glibc version '{s}': {s}", .{ line_i, line, @errorName(err) }); return error.ZigInstallationCorrupt; }; try all_versions.append(arena, ver); @@ -136,7 +136,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! return error.ZigInstallationCorrupt; }; const lib = findLib(lib_name) orelse { - std.log.err("fns.txt:{}: unknown library name: {}", .{ line_i, lib_name }); + std.log.err("fns.txt:{}: unknown library name: {s}", .{ line_i, lib_name }); return error.ZigInstallationCorrupt; }; try all_functions.append(arena, .{ @@ -170,15 +170,15 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! return error.ZigInstallationCorrupt; }; const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse { - std.log.err("abi.txt:{}: unrecognized arch: '{}'", .{ line_i, arch_name }); + std.log.err("abi.txt:{}: unrecognized arch: '{s}'", .{ line_i, arch_name }); return error.ZigInstallationCorrupt; }; if (!mem.eql(u8, os_name, "linux")) { - std.log.err("abi.txt:{}: expected OS 'linux', found '{}'", .{ line_i, os_name }); + std.log.err("abi.txt:{}: expected OS 'linux', found '{s}'", .{ line_i, os_name }); return error.ZigInstallationCorrupt; } const abi_tag = std.meta.stringToEnum(std.Target.Abi, abi_name) orelse { - std.log.err("abi.txt:{}: unrecognized ABI: '{}'", .{ line_i, abi_name }); + std.log.err("abi.txt:{}: unrecognized ABI: '{s}'", .{ line_i, abi_name }); return error.ZigInstallationCorrupt; }; @@ -211,7 +211,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! } const version_index = std.fmt.parseInt(u8, version_index_string, 10) catch |err| { // If this happens with legit data, increase the size of the integer type in the struct. - std.log.err("abi.txt:{}: unable to parse version: {}", .{ line_i, @errorName(err) }); + std.log.err("abi.txt:{}: unable to parse version: {s}", .{ line_i, @errorName(err) }); return error.ZigInstallationCorrupt; }; @@ -531,7 +531,7 @@ fn add_include_dirs(comp: *Compilation, arena: *Allocator, args: *std.ArrayList( try args.append(try path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, lib_libc ++ "glibc" })); try args.append("-I"); - try args.append(try std.fmt.allocPrint(arena, "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-{}", .{ + try args.append(try std.fmt.allocPrint(arena, "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-{s}", .{ comp.zig_lib_directory.path.?, @tagName(arch), @tagName(target.os.tag), @tagName(target.abi), })); @@ -539,7 +539,7 @@ fn add_include_dirs(comp: *Compilation, arena: *Allocator, args: *std.ArrayList( try args.append(try lib_path(comp, arena, lib_libc ++ "include" ++ s ++ "generic-glibc")); try args.append("-I"); - try args.append(try std.fmt.allocPrint(arena, "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-linux-any", .{ + try args.append(try std.fmt.allocPrint(arena, "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-linux-any", .{ comp.zig_lib_directory.path.?, @tagName(arch), })); @@ -881,7 +881,7 @@ pub fn buildSharedObjects(comp: *Compilation) !void { if (o_directory.handle.createFile(ok_basename, .{})) |file| { file.close(); } else |err| { - std.log.warn("glibc shared objects: failed to mark completion: {}", .{@errorName(err)}); + std.log.warn("glibc shared objects: failed to mark completion: {s}", .{@errorName(err)}); } } diff --git a/src/libc_installation.zig b/src/libc_installation.zig index 0b1029eeb8..cc96146e0b 100644 --- a/src/libc_installation.zig +++ b/src/libc_installation.zig @@ -83,7 +83,7 @@ pub const LibCInstallation = struct { } inline for (fields) |field, i| { if (!found_keys[i].found) { - log.err("missing field: {}\n", .{field.name}); + log.err("missing field: {s}\n", .{field.name}); return error.ParseError; } } @@ -96,18 +96,18 @@ pub const LibCInstallation = struct { return error.ParseError; } if (self.crt_dir == null and !is_darwin) { - log.err("crt_dir may not be empty for {}\n", .{@tagName(Target.current.os.tag)}); + log.err("crt_dir may not be empty for {s}\n", .{@tagName(Target.current.os.tag)}); return error.ParseError; } if (self.msvc_lib_dir == null and is_windows and !is_gnu) { - log.err("msvc_lib_dir may not be empty for {}-{}\n", .{ + log.err("msvc_lib_dir may not be empty for {s}-{s}\n", .{ @tagName(Target.current.os.tag), @tagName(Target.current.abi), }); return error.ParseError; } if (self.kernel32_lib_dir == null and is_windows and !is_gnu) { - log.err("kernel32_lib_dir may not be empty for {}-{}\n", .{ + log.err("kernel32_lib_dir may not be empty for {s}-{s}\n", .{ @tagName(Target.current.os.tag), @tagName(Target.current.abi), }); @@ -128,25 +128,25 @@ pub const LibCInstallation = struct { try out.print( \\# The directory that contains `stdlib.h`. \\# On POSIX-like systems, include directories be found with: `cc -E -Wp,-v -xc /dev/null` - \\include_dir={} + \\include_dir={s} \\ \\# The system-specific include directory. May be the same as `include_dir`. \\# On Windows it's the directory that includes `vcruntime.h`. \\# On POSIX it's the directory that includes `sys/errno.h`. - \\sys_include_dir={} + \\sys_include_dir={s} \\ \\# The directory that contains `crt1.o` or `crt2.o`. \\# On POSIX, can be found with `cc -print-file-name=crt1.o`. \\# Not needed when targeting MacOS. - \\crt_dir={} + \\crt_dir={s} \\ \\# The directory that contains `vcruntime.lib`. \\# Only needed when targeting MSVC on Windows. - \\msvc_lib_dir={} + \\msvc_lib_dir={s} \\ \\# The directory that contains `kernel32.lib`. \\# Only needed when targeting MSVC on Windows. - \\kernel32_lib_dir={} + \\kernel32_lib_dir={s} \\ , .{ include_dir, @@ -338,7 +338,7 @@ pub const LibCInstallation = struct { for (searches) |search| { result_buf.shrink(0); - try result_buf.outStream().print("{}\\Include\\{}\\ucrt", .{ search.path, search.version }); + try result_buf.outStream().print("{s}\\Include\\{s}\\ucrt", .{ search.path, search.version }); var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, @@ -384,7 +384,7 @@ pub const LibCInstallation = struct { for (searches) |search| { result_buf.shrink(0); - try result_buf.outStream().print("{}\\Lib\\{}\\ucrt\\{}", .{ search.path, search.version, arch_sub_dir }); + try result_buf.outStream().print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ search.path, search.version, arch_sub_dir }); var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, @@ -439,7 +439,7 @@ pub const LibCInstallation = struct { for (searches) |search| { result_buf.shrink(0); const stream = result_buf.outStream(); - try stream.print("{}\\Lib\\{}\\um\\{}", .{ search.path, search.version, arch_sub_dir }); + try stream.print("{s}\\Lib\\{s}\\um\\{s}", .{ search.path, search.version, arch_sub_dir }); var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, @@ -520,7 +520,7 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { const allocator = args.allocator; const cc_exe = std.os.getenvZ("CC") orelse default_cc_exe; - const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={}", .{args.search_basename}); + const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={s}", .{args.search_basename}); defer allocator.free(arg1); const argv = [_][]const u8{ cc_exe, arg1 }; @@ -584,17 +584,17 @@ fn printVerboseInvocation( if (!verbose) return; if (search_basename) |s| { - std.debug.warn("Zig attempted to find the file '{}' by executing this command:\n", .{s}); + std.debug.warn("Zig attempted to find the file '{s}' by executing this command:\n", .{s}); } else { std.debug.warn("Zig attempted to find the path to native system libc headers by executing this command:\n", .{}); } for (argv) |arg, i| { if (i != 0) std.debug.warn(" ", .{}); - std.debug.warn("{}", .{arg}); + std.debug.warn("{s}", .{arg}); } std.debug.warn("\n", .{}); if (stderr) |s| { - std.debug.warn("Output:\n==========\n{}\n==========\n", .{s}); + std.debug.warn("Output:\n==========\n{s}\n==========\n", .{s}); } } diff --git a/src/link.zig b/src/link.zig index 468f23bffd..92702c7973 100644 --- a/src/link.zig +++ b/src/link.zig @@ -560,9 +560,9 @@ pub const File = struct { const full_out_path_z = try arena.dupeZ(u8, full_out_path); if (base.options.verbose_link) { - std.debug.print("ar rcs {}", .{full_out_path_z}); + std.debug.print("ar rcs {s}", .{full_out_path_z}); for (object_files.items) |arg| { - std.debug.print(" {}", .{arg}); + std.debug.print(" {s}", .{arg}); } std.debug.print("\n", .{}); } @@ -574,11 +574,11 @@ pub const File = struct { if (!base.options.disable_lld_caching) { Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save archive hash digest file: {}", .{@errorName(err)}); + log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)}); }; man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when archiving: {}", .{@errorName(err)}); + log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)}); }; base.lock = man.toOwnedLock(); diff --git a/src/link/C.zig b/src/link/C.zig index 1059e52115..3ac9db717f 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -111,8 +111,11 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { if (self.header.buf.items.len > 0) { try writer.writeByte('\n'); } + if (self.header.items.len > 0) { + try writer.print("{s}\n", .{self.header.items}); + } if (self.constants.items.len > 0) { - try writer.print("{}\n", .{self.constants.items}); + try writer.print("{s}\n", .{self.constants.items}); } if (self.main.items.len > 1) { const last_two = self.main.items[self.main.items.len - 2 ..]; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 096fa2cd0b..a3cb5cc4c7 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -686,7 +686,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { if (need_realloc) { const curr_vaddr = self.getDeclVAddr(decl); const vaddr = try self.growTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, curr_vaddr, vaddr }); + log.debug("growing {s} from 0x{x} to 0x{x}\n", .{ decl.name, curr_vaddr, vaddr }); if (vaddr != curr_vaddr) { log.debug(" (writing new offset table entry)\n", .{}); self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; @@ -697,7 +697,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { } } else { const vaddr = try self.allocateTextBlock(&decl.link.coff, code.len, required_alignment); - log.debug("allocated text block for {} at 0x{x} (size: {Bi})\n", .{ mem.spanZ(decl.name), vaddr, code.len }); + log.debug("allocated text block for {s} at 0x{x} (size: {Bi})\n", .{ mem.spanZ(decl.name), vaddr, code.len }); errdefer self.freeTextBlock(&decl.link.coff); self.offset_table.items[decl.link.coff.offset_table_index] = vaddr; try self.writeOffsetTableEntry(decl.link.coff.offset_table_index); @@ -880,7 +880,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("COFF LLD new_digest={} error: {}", .{ digest, @errorName(err) }); + log.debug("COFF LLD new_digest={} error: {s}", .{ digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -1236,11 +1236,11 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { // Update the file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {}", .{@errorName(err)}); + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); }; // Again failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)}); + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); }; // We hang on to this lock so that the output file path can be used without // other processes clobbering it. diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 4b2b95fc72..2db15ae280 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1362,7 +1362,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("ELF LLD new_digest={} error: {}", .{ digest, @errorName(err) }); + log.debug("ELF LLD new_digest={} error: {s}", .{ digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -1396,7 +1396,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { if (self.base.options.output_mode == .Exe) { try argv.append("-z"); - try argv.append(try std.fmt.allocPrint(arena, "stack-size={}", .{stack_size})); + try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size})); } if (self.base.options.image_base_override) |image_base| { @@ -1438,7 +1438,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { if (getLDMOption(target)) |ldm| { // Any target ELF will use the freebsd osabi if suffixed with "_fbsd". const arg = if (target.os.tag == .freebsd) - try std.fmt.allocPrint(arena, "{}_fbsd", .{ldm}) + try std.fmt.allocPrint(arena, "{s}_fbsd", .{ldm}) else ldm; try argv.append("-m"); @@ -1599,7 +1599,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { // (the check for that needs to be earlier), but they could be full paths to .so files, in which // case we want to avoid prepending "-l". const ext = Compilation.classifyFileExt(link_lib); - const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{}", .{link_lib}); + const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{s}", .{link_lib}); argv.appendAssumeCapacity(arg); } @@ -1733,11 +1733,11 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { // Update the file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {}", .{@errorName(err)}); + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); }; // Again failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)}); + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); }; // We hang on to this lock so that the output file path can be used without // other processes clobbering it. @@ -2082,10 +2082,10 @@ pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); if (self.local_symbol_free_list.popOrNull()) |i| { - log.debug("reusing symbol index {} for {}\n", .{ i, decl.name }); + log.debug("reusing symbol index {} for {s}\n", .{ i, decl.name }); decl.link.elf.local_sym_index = i; } else { - log.debug("allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name }); + log.debug("allocating symbol index {} for {s}\n", .{ self.local_symbols.items.len, decl.name }); decl.link.elf.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -2182,7 +2182,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { if (zir_dumps.len != 0) { for (zir_dumps) |fn_name| { if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) { - std.debug.print("\n{}\n", .{decl.name}); + std.debug.print("\n{s}\n", .{decl.name}); typed_value.val.castTag(.function).?.data.dump(module.*); } } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6abbae2c26..d4a61c8149 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -520,7 +520,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("MachO LLD new_digest={} error: {}", .{ digest, @errorName(err) }); + log.debug("MachO LLD new_digest={} error: {s}", .{ digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -706,7 +706,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which // case we want to avoid prepending "-l". const ext = Compilation.classifyFileExt(link_lib); - const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{}", .{link_lib}); + const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{s}", .{link_lib}); argv.appendAssumeCapacity(arg); } @@ -759,15 +759,15 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { self.base.allocator.free(result.stderr); } if (result.stdout.len != 0) { - log.warn("unexpected LD stdout: {}", .{result.stdout}); + log.warn("unexpected LD stdout: {s}", .{result.stdout}); } if (result.stderr.len != 0) { - log.warn("unexpected LD stderr: {}", .{result.stderr}); + log.warn("unexpected LD stderr: {s}", .{result.stderr}); } if (result.term != .Exited or result.term.Exited != 0) { // TODO parse this output and surface with the Compilation API rather than // directly outputting to stderr here. - log.err("{}", .{result.stderr}); + log.err("{s}", .{result.stderr}); return error.LDReportedFailure; } } else { @@ -980,11 +980,11 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { // Update the file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest file: {}", .{@errorName(err)}); + log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)}); }; // Again failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)}); + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); }; // We hang on to this lock so that the output file path can be used without // other processes clobbering it. @@ -1088,10 +1088,10 @@ pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void { try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); if (self.local_symbol_free_list.popOrNull()) |i| { - log.debug("reusing symbol index {} for {}", .{ i, decl.name }); + log.debug("reusing symbol index {d} for {s}", .{ i, decl.name }); decl.link.macho.local_sym_index = i; } else { - log.debug("allocating symbol index {} for {}", .{ self.local_symbols.items.len, decl.name }); + log.debug("allocating symbol index {d} for {s}", .{ self.local_symbols.items.len, decl.name }); decl.link.macho.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -1165,7 +1165,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, symbol.n_value, required_alignment); if (need_realloc) { const vaddr = try self.growTextBlock(&decl.link.macho, code.len, required_alignment); - log.debug("growing {} from 0x{x} to 0x{x}", .{ decl.name, symbol.n_value, vaddr }); + log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl.name, symbol.n_value, vaddr }); if (vaddr != symbol.n_value) { symbol.n_value = vaddr; log.debug(" (writing new offset table entry)", .{}); @@ -1188,7 +1188,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); const addr = try self.allocateTextBlock(&decl.link.macho, code.len, required_alignment); - log.debug("allocated text block for {} at 0x{x}", .{ decl_name, addr }); + log.debug("allocated text block for {s} at 0x{x}", .{ decl_name, addr }); errdefer self.freeTextBlock(&decl.link.macho); symbol.* = .{ diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 374fa2230b..cbb3e83147 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -321,7 +321,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("WASM LLD new_digest={} error: {}", .{ digest, @errorName(err) }); + log.debug("WASM LLD new_digest={} error: {s}", .{ digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -463,11 +463,11 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { // Update the file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { - log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)}); + log.warn("failed to save linking hash digest symlink: {s}", .{@errorName(err)}); }; // Again failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)}); + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); }; // We hang on to this lock so that the output file path can be used without // other processes clobbering it. diff --git a/src/main.zig b/src/main.zig index 98b19cc3ea..7d03ea956f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -118,7 +118,7 @@ pub fn main() anyerror!void { pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !void { if (args.len <= 1) { - std.log.info("{}", .{usage}); + std.log.info("{s}", .{usage}); fatal("expected command argument", .{}); } @@ -204,8 +204,8 @@ pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v } else if (mem.eql(u8, cmd, "help") or mem.eql(u8, cmd, "-h") or mem.eql(u8, cmd, "--help")) { try io.getStdOut().writeAll(usage); } else { - std.log.info("{}", .{usage}); - fatal("unknown command: {}", .{args[1]}); + std.log.info("{s}", .{usage}); + fatal("unknown command: {s}", .{args[1]}); } } @@ -615,7 +615,7 @@ fn buildOutputType( fatal("unexpected end-of-parameter mark: --", .{}); } } else if (mem.eql(u8, arg, "--pkg-begin")) { - if (i + 2 >= args.len) fatal("Expected 2 arguments after {}", .{arg}); + if (i + 2 >= args.len) fatal("Expected 2 arguments after {s}", .{arg}); i += 1; const pkg_name = args[i]; i += 1; @@ -635,7 +635,7 @@ fn buildOutputType( cur_pkg = cur_pkg.parent orelse fatal("encountered --pkg-end with no matching --pkg-begin", .{}); } else if (mem.eql(u8, arg, "--main-pkg-path")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; main_pkg_path = args[i]; } else if (mem.eql(u8, arg, "-cflags")) { @@ -653,10 +653,10 @@ fn buildOutputType( i += 1; const next_arg = args[i]; color = std.meta.stringToEnum(Color, next_arg) orelse { - fatal("expected [auto|on|off] after --color, found '{}'", .{next_arg}); + fatal("expected [auto|on|off] after --color, found '{s}'", .{next_arg}); }; } else if (mem.eql(u8, arg, "--subsystem")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; if (mem.eql(u8, args[i], "console")) { subsystem = .Console; @@ -689,51 +689,51 @@ fn buildOutputType( }); } } else if (mem.eql(u8, arg, "-O")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; optimize_mode_string = args[i]; } else if (mem.eql(u8, arg, "--stack")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; stack_size_override = std.fmt.parseUnsigned(u64, args[i], 0) catch |err| { fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "--image-base")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; image_base_override = std.fmt.parseUnsigned(u64, args[i], 0) catch |err| { fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "--name")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; provided_name = args[i]; } else if (mem.eql(u8, arg, "-rpath")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; try rpath_list.append(args[i]); } else if (mem.eql(u8, arg, "--library-directory") or mem.eql(u8, arg, "-L")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; try lib_dirs.append(args[i]); } else if (mem.eql(u8, arg, "-F")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; try framework_dirs.append(args[i]); } else if (mem.eql(u8, arg, "-framework")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; try frameworks.append(args[i]); } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; linker_script = args[i]; } else if (mem.eql(u8, arg, "--version-script")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; version_script = args[i]; } else if (mem.eql(u8, arg, "--library") or mem.eql(u8, arg, "-l")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); // We don't know whether this library is part of libc or libc++ until we resolve the target. // So we simply append to the list for now. i += 1; @@ -743,7 +743,7 @@ fn buildOutputType( mem.eql(u8, arg, "-I") or mem.eql(u8, arg, "-dirafter")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; try clang_argv.append(arg); try clang_argv.append(args[i]); @@ -753,19 +753,19 @@ fn buildOutputType( } i += 1; version = std.builtin.Version.parse(args[i]) catch |err| { - fatal("unable to parse --version '{}': {}", .{ args[i], @errorName(err) }); + fatal("unable to parse --version '{s}': {s}", .{ args[i], @errorName(err) }); }; have_version = true; } else if (mem.eql(u8, arg, "-target")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; target_arch_os_abi = args[i]; } else if (mem.eql(u8, arg, "-mcpu")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; target_mcpu = args[i]; } else if (mem.eql(u8, arg, "-mcmodel")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; machine_code_model = parseCodeModel(args[i]); } else if (mem.startsWith(u8, arg, "-ofmt=")) { @@ -777,35 +777,35 @@ fn buildOutputType( } else if (mem.startsWith(u8, arg, "-O")) { optimize_mode_string = arg["-O".len..]; } else if (mem.eql(u8, arg, "--dynamic-linker")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; target_dynamic_linker = args[i]; } else if (mem.eql(u8, arg, "--libc")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; libc_paths_file = args[i]; } else if (mem.eql(u8, arg, "--test-filter")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; test_filter = args[i]; } else if (mem.eql(u8, arg, "--test-name-prefix")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; test_name_prefix = args[i]; } else if (mem.eql(u8, arg, "--test-cmd")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; try test_exec_args.append(args[i]); } else if (mem.eql(u8, arg, "--cache-dir")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; override_local_cache_dir = args[i]; } else if (mem.eql(u8, arg, "--global-cache-dir")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; override_global_cache_dir = args[i]; } else if (mem.eql(u8, arg, "--override-lib-dir")) { - if (i + 1 >= args.len) fatal("expected parameter after {}", .{arg}); + if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; override_lib_dir = args[i]; } else if (mem.eql(u8, arg, "-fcompiler-rt")) { @@ -968,7 +968,7 @@ fn buildOutputType( { try clang_argv.append(arg); } else { - fatal("unrecognized parameter: '{}'", .{arg}); + fatal("unrecognized parameter: '{s}'", .{arg}); } } else switch (Compilation.classifyFileExt(arg)) { .object, .static_library, .shared_library => { @@ -982,19 +982,19 @@ fn buildOutputType( }, .zig, .zir => { if (root_src_file) |other| { - fatal("found another zig file '{}' after root source file '{}'", .{ arg, other }); + fatal("found another zig file '{s}' after root source file '{s}'", .{ arg, other }); } else { root_src_file = arg; } }, .unknown => { - fatal("unrecognized file extension of parameter '{}'", .{arg}); + fatal("unrecognized file extension of parameter '{s}'", .{arg}); }, } } if (optimize_mode_string) |s| { optimize_mode = std.meta.stringToEnum(std.builtin.Mode, s) orelse - fatal("unrecognized optimization mode: '{}'", .{s}); + fatal("unrecognized optimization mode: '{s}'", .{s}); } }, .cc, .cpp => { @@ -1018,7 +1018,7 @@ fn buildOutputType( var it = ClangArgIterator.init(arena, all_args); while (it.has_next) { it.next() catch |err| { - fatal("unable to parse command line parameters: {}", .{@errorName(err)}); + fatal("unable to parse command line parameters: {s}", .{@errorName(err)}); }; switch (it.zig_equivalent) { .target => target_arch_os_abi = it.only_arg, // example: -target riscv64-linux-unknown @@ -1038,7 +1038,7 @@ fn buildOutputType( }, .zig, .zir => { if (root_src_file) |other| { - fatal("found another zig file '{}' after root source file '{}'", .{ it.only_arg, other }); + fatal("found another zig file '{s}' after root source file '{s}'", .{ it.only_arg, other }); } else { root_src_file = it.only_arg; } @@ -1153,7 +1153,7 @@ fn buildOutputType( if (mem.eql(u8, arg, "-soname")) { i += 1; if (i >= linker_args.items.len) { - fatal("expected linker arg after '{}'", .{arg}); + fatal("expected linker arg after '{s}'", .{arg}); } const name = linker_args.items[i]; soname = .{ .yes = name }; @@ -1185,7 +1185,7 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-rpath")) { i += 1; if (i >= linker_args.items.len) { - fatal("expected linker arg after '{}'", .{arg}); + fatal("expected linker arg after '{s}'", .{arg}); } try rpath_list.append(linker_args.items[i]); } else if (mem.eql(u8, arg, "-I") or @@ -1194,7 +1194,7 @@ fn buildOutputType( { i += 1; if (i >= linker_args.items.len) { - fatal("expected linker arg after '{}'", .{arg}); + fatal("expected linker arg after '{s}'", .{arg}); } target_dynamic_linker = linker_args.items[i]; } else if (mem.eql(u8, arg, "-E") or @@ -1205,7 +1205,7 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--version-script")) { i += 1; if (i >= linker_args.items.len) { - fatal("expected linker arg after '{}'", .{arg}); + fatal("expected linker arg after '{s}'", .{arg}); } version_script = linker_args.items[i]; } else if (mem.startsWith(u8, arg, "-O")) { @@ -1227,7 +1227,7 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-z")) { i += 1; if (i >= linker_args.items.len) { - fatal("expected linker arg after '{}'", .{arg}); + fatal("expected linker arg after '{s}'", .{arg}); } const z_arg = linker_args.items[i]; if (mem.eql(u8, z_arg, "nodelete")) { @@ -1235,44 +1235,44 @@ fn buildOutputType( } else if (mem.eql(u8, z_arg, "defs")) { linker_z_defs = true; } else { - warn("unsupported linker arg: -z {}", .{z_arg}); + warn("unsupported linker arg: -z {s}", .{z_arg}); } } else if (mem.eql(u8, arg, "--major-image-version")) { i += 1; if (i >= linker_args.items.len) { - fatal("expected linker arg after '{}'", .{arg}); + fatal("expected linker arg after '{s}'", .{arg}); } version.major = std.fmt.parseUnsigned(u32, linker_args.items[i], 10) catch |err| { - fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; have_version = true; } else if (mem.eql(u8, arg, "--minor-image-version")) { i += 1; if (i >= linker_args.items.len) { - fatal("expected linker arg after '{}'", .{arg}); + fatal("expected linker arg after '{s}'", .{arg}); } version.minor = std.fmt.parseUnsigned(u32, linker_args.items[i], 10) catch |err| { - fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; have_version = true; } else if (mem.eql(u8, arg, "--stack")) { i += 1; if (i >= linker_args.items.len) { - fatal("expected linker arg after '{}'", .{arg}); + fatal("expected linker arg after '{s}'", .{arg}); } stack_size_override = std.fmt.parseUnsigned(u64, linker_args.items[i], 0) catch |err| { - fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "--image-base")) { i += 1; if (i >= linker_args.items.len) { - fatal("expected linker arg after '{}'", .{arg}); + fatal("expected linker arg after '{s}'", .{arg}); } image_base_override = std.fmt.parseUnsigned(u64, linker_args.items[i], 0) catch |err| { - fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; } else { - warn("unsupported linker arg: {}", .{arg}); + warn("unsupported linker arg: {s}", .{arg}); } } @@ -1328,7 +1328,7 @@ fn buildOutputType( } if (arg_mode == .translate_c and c_source_files.items.len != 1) { - fatal("translate-c expects exactly 1 source file (found {})", .{c_source_files.items.len}); + fatal("translate-c expects exactly 1 source file (found {d})", .{c_source_files.items.len}); } if (root_src_file == null and arg_mode == .zig_test) { @@ -1373,25 +1373,25 @@ fn buildOutputType( help: { var help_text = std.ArrayList(u8).init(arena); for (diags.arch.?.allCpuModels()) |cpu| { - help_text.writer().print(" {}\n", .{cpu.name}) catch break :help; + help_text.writer().print(" {s}\n", .{cpu.name}) catch break :help; } - std.log.info("Available CPUs for architecture '{}': {}", .{ + std.log.info("Available CPUs for architecture '{s}': {s}", .{ @tagName(diags.arch.?), help_text.items, }); } - fatal("Unknown CPU: '{}'", .{diags.cpu_name.?}); + fatal("Unknown CPU: '{s}'", .{diags.cpu_name.?}); }, error.UnknownCpuFeature => { help: { var help_text = std.ArrayList(u8).init(arena); for (diags.arch.?.allFeaturesList()) |feature| { - help_text.writer().print(" {}: {}\n", .{ feature.name, feature.description }) catch break :help; + help_text.writer().print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help; } - std.log.info("Available CPU features for architecture '{}': {}", .{ + std.log.info("Available CPU features for architecture '{s}': {s}", .{ @tagName(diags.arch.?), help_text.items, }); } - fatal("Unknown CPU feature: '{}'", .{diags.unknown_feature_name}); + fatal("Unknown CPU feature: '{s}'", .{diags.unknown_feature_name}); }, else => |e| return e, }; @@ -1431,10 +1431,10 @@ fn buildOutputType( if (cross_target.isNativeOs() and (system_libs.items.len != 0 or want_native_include_dirs)) { const paths = std.zig.system.NativePaths.detect(arena) catch |err| { - fatal("unable to detect native system paths: {}", .{@errorName(err)}); + fatal("unable to detect native system paths: {s}", .{@errorName(err)}); }; for (paths.warnings.items) |warning| { - warn("{}", .{warning}); + warn("{s}", .{warning}); } const has_sysroot = if (comptime std.Target.current.isDarwin()) outer: { @@ -1492,7 +1492,7 @@ fn buildOutputType( } else if (mem.eql(u8, ofmt, "raw")) { break :blk .raw; } else { - fatal("unsupported object format: {}", .{ofmt}); + fatal("unsupported object format: {s}", .{ofmt}); } }; @@ -1562,7 +1562,7 @@ fn buildOutputType( } if (fs.path.dirname(full_path)) |dirname| { const handle = fs.cwd().openDir(dirname, .{}) catch |err| { - fatal("unable to open output directory '{}': {}", .{ dirname, @errorName(err) }); + fatal("unable to open output directory '{s}': {s}", .{ dirname, @errorName(err) }); }; cleanup_emit_bin_dir = handle; break :b Compilation.EmitLoc{ @@ -1585,19 +1585,19 @@ fn buildOutputType( }, }; - const default_h_basename = try std.fmt.allocPrint(arena, "{}.h", .{root_name}); + const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name}); var emit_h_resolved = try emit_h.resolve(default_h_basename); defer emit_h_resolved.deinit(); - const default_asm_basename = try std.fmt.allocPrint(arena, "{}.s", .{root_name}); + const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name}); var emit_asm_resolved = try emit_asm.resolve(default_asm_basename); defer emit_asm_resolved.deinit(); - const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{}.ll", .{root_name}); + const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name}); var emit_llvm_ir_resolved = try emit_llvm_ir.resolve(default_llvm_ir_basename); defer emit_llvm_ir_resolved.deinit(); - const default_analysis_basename = try std.fmt.allocPrint(arena, "{}-analysis.json", .{root_name}); + const default_analysis_basename = try std.fmt.allocPrint(arena, "{s}-analysis.json", .{root_name}); var emit_analysis_resolved = try emit_analysis.resolve(default_analysis_basename); defer emit_analysis_resolved.deinit(); @@ -1609,10 +1609,10 @@ fn buildOutputType( .yes_default_path => blk: { if (root_src_file) |rsf| { if (mem.endsWith(u8, rsf, ".zir")) { - break :blk try std.fmt.allocPrint(arena, "{}.out.zir", .{root_name}); + break :blk try std.fmt.allocPrint(arena, "{s}.out.zir", .{root_name}); } } - break :blk try std.fmt.allocPrint(arena, "{}.zir", .{root_name}); + break :blk try std.fmt.allocPrint(arena, "{s}.zir", .{root_name}); }, .yes => |p| p, }; @@ -1642,7 +1642,7 @@ fn buildOutputType( } else introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { - fatal("unable to find zig installation directory: {}", .{@errorName(err)}); + fatal("unable to find zig installation directory: {s}", .{@errorName(err)}); }; defer zig_lib_directory.handle.close(); @@ -1655,7 +1655,7 @@ fn buildOutputType( if (libc_paths_file) |paths_file| { libc_installation = LibCInstallation.parse(gpa, paths_file) catch |err| { - fatal("unable to parse libc paths file: {}", .{@errorName(err)}); + fatal("unable to parse libc paths file: {s}", .{@errorName(err)}); }; } @@ -1791,7 +1791,7 @@ fn buildOutputType( .disable_lld_caching = !have_enable_cache, .subsystem = subsystem, }) catch |err| { - fatal("unable to create compilation: {}", .{@errorName(err)}); + fatal("unable to create compilation: {s}", .{@errorName(err)}); }; var comp_destroyed = false; defer if (!comp_destroyed) comp.destroy(); @@ -1914,12 +1914,12 @@ fn buildOutputType( if (!watch) return cleanExit(); } else { const cmd = try argvCmd(arena, argv.items); - fatal("the following test command failed with exit code {}:\n{}", .{ code, cmd }); + fatal("the following test command failed with exit code {}:\n{s}", .{ code, cmd }); } }, else => { const cmd = try argvCmd(arena, argv.items); - fatal("the following test command crashed:\n{}", .{cmd}); + fatal("the following test command crashed:\n{s}", .{cmd}); }, } }, @@ -1936,7 +1936,7 @@ fn buildOutputType( try stderr.print("(zig) ", .{}); try comp.makeBinFileExecutable(); if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| { - try stderr.print("\nUnable to parse command: {}\n", .{@errorName(err)}); + try stderr.print("\nUnable to parse command: {s}\n", .{@errorName(err)}); continue; }) |line| { const actual_line = mem.trimRight(u8, line, "\r\n "); @@ -1954,7 +1954,7 @@ fn buildOutputType( } else if (mem.eql(u8, actual_line, "help")) { try stderr.writeAll(repl_help); } else { - try stderr.print("unknown command: {}\n", .{actual_line}); + try stderr.print("unknown command: {s}\n", .{actual_line}); } } else { break; @@ -2012,14 +2012,14 @@ fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !voi assert(comp.c_source_files.len == 1); const c_source_file = comp.c_source_files[0]; - const translated_zig_basename = try std.fmt.allocPrint(arena, "{}.zig", .{comp.bin_file.options.root_name}); + const translated_zig_basename = try std.fmt.allocPrint(arena, "{s}.zig", .{comp.bin_file.options.root_name}); var man: Cache.Manifest = comp.obtainCObjectCacheManifest(); defer if (enable_cache) man.deinit(); man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects _ = man.addFile(c_source_file.src_path, null) catch |err| { - fatal("unable to process '{}': {}", .{ c_source_file.src_path, @errorName(err) }); + fatal("unable to process '{s}': {s}", .{ c_source_file.src_path, @errorName(err) }); }; const digest = if (try man.hit()) man.final() else digest: { @@ -2034,7 +2034,7 @@ fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !voi break :blk null; const c_src_basename = fs.path.basename(c_source_file.src_path); - const dep_basename = try std.fmt.allocPrint(arena, "{}.d", .{c_src_basename}); + const dep_basename = try std.fmt.allocPrint(arena, "{s}.d", .{c_src_basename}); const out_dep_path = try comp.tmpFilePath(arena, dep_basename); break :blk out_dep_path; }; @@ -2069,7 +2069,7 @@ fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !voi error.ASTUnitFailure => fatal("clang API returned errors but due to a clang bug, it is not exposing the errors for zig to see. For more details: https://github.com/ziglang/zig/issues/4455", .{}), error.SemanticAnalyzeFail => { for (clang_errors) |clang_err| { - std.debug.print("{}:{}:{}: {}\n", .{ + std.debug.print("{s}:{}:{}: {s}\n", .{ if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)", clang_err.line + 1, clang_err.column + 1, @@ -2087,7 +2087,7 @@ fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !voi try man.addDepFilePost(zig_cache_tmp_dir, dep_basename); // Just to save disk space, we delete the file because it is never needed again. zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| { - warn("failed to delete '{}': {}", .{ dep_file_path, @errorName(err) }); + warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }); }; } @@ -2102,7 +2102,7 @@ fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !voi _ = try std.zig.render(comp.gpa, bos.writer(), tree); try bos.flush(); - man.writeManifest() catch |err| warn("failed to write cache manifest: {}", .{@errorName(err)}); + man.writeManifest() catch |err| warn("failed to write cache manifest: {s}", .{@errorName(err)}); break :digest digest; }; @@ -2111,7 +2111,7 @@ fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !voi const full_zig_path = try comp.local_cache_directory.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename, }); - try io.getStdOut().writer().print("{}\n", .{full_zig_path}); + try io.getStdOut().writer().print("{s}\n", .{full_zig_path}); return cleanExit(); } else { const out_zig_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename }); @@ -2148,10 +2148,10 @@ pub fn cmdLibC(gpa: *Allocator, args: []const []const u8) !void { try stdout.writeAll(usage_libc); return cleanExit(); } else { - fatal("unrecognized parameter: '{}'", .{arg}); + fatal("unrecognized parameter: '{s}'", .{arg}); } } else if (input_file != null) { - fatal("unexpected extra parameter: '{}'", .{arg}); + fatal("unexpected extra parameter: '{s}'", .{arg}); } else { input_file = arg; } @@ -2159,7 +2159,7 @@ pub fn cmdLibC(gpa: *Allocator, args: []const []const u8) !void { } if (input_file) |libc_file| { var libc = LibCInstallation.parse(gpa, libc_file) catch |err| { - fatal("unable to parse libc file: {}", .{@errorName(err)}); + fatal("unable to parse libc file: {s}", .{@errorName(err)}); }; defer libc.deinit(gpa); } else { @@ -2167,7 +2167,7 @@ pub fn cmdLibC(gpa: *Allocator, args: []const []const u8) !void { .allocator = gpa, .verbose = true, }) catch |err| { - fatal("unable to detect native libc: {}", .{@errorName(err)}); + fatal("unable to detect native libc: {s}", .{@errorName(err)}); }; defer libc.deinit(gpa); @@ -2205,16 +2205,16 @@ pub fn cmdInit( try io.getStdOut().writeAll(usage_init); return cleanExit(); } else { - fatal("unrecognized parameter: '{}'", .{arg}); + fatal("unrecognized parameter: '{s}'", .{arg}); } } else { - fatal("unexpected extra parameter: '{}'", .{arg}); + fatal("unexpected extra parameter: '{s}'", .{arg}); } } } const self_exe_path = try fs.selfExePathAlloc(arena); var zig_lib_directory = introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { - fatal("unable to find zig installation directory: {}\n", .{@errorName(err)}); + fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); }; defer zig_lib_directory.handle.close(); @@ -2232,7 +2232,7 @@ pub fn cmdInit( const max_bytes = 10 * 1024 * 1024; const build_zig_contents = template_dir.readFileAlloc(arena, "build.zig", max_bytes) catch |err| { - fatal("unable to read template file 'build.zig': {}", .{@errorName(err)}); + fatal("unable to read template file 'build.zig': {s}", .{@errorName(err)}); }; var modified_build_zig_contents = std.ArrayList(u8).init(arena); try modified_build_zig_contents.ensureCapacity(build_zig_contents.len); @@ -2244,13 +2244,13 @@ pub fn cmdInit( } } const main_zig_contents = template_dir.readFileAlloc(arena, "src" ++ s ++ "main.zig", max_bytes) catch |err| { - fatal("unable to read template file 'main.zig': {}", .{@errorName(err)}); + fatal("unable to read template file 'main.zig': {s}", .{@errorName(err)}); }; if (fs.cwd().access("build.zig", .{})) |_| { fatal("existing build.zig file would be overwritten", .{}); } else |err| switch (err) { error.FileNotFound => {}, - else => fatal("unable to test existence of build.zig: {}\n", .{@errorName(err)}), + else => fatal("unable to test existence of build.zig: {s}\n", .{@errorName(err)}), } var src_dir = try fs.cwd().makeOpenPath("src", .{}); defer src_dir.close(); @@ -2311,23 +2311,23 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "--build-file")) { - if (i + 1 >= args.len) fatal("expected argument after '{}'", .{arg}); + if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); i += 1; build_file = args[i]; continue; } else if (mem.eql(u8, arg, "--override-lib-dir")) { - if (i + 1 >= args.len) fatal("expected argument after '{}'", .{arg}); + if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); i += 1; override_lib_dir = args[i]; try child_argv.appendSlice(&[_][]const u8{ arg, args[i] }); continue; } else if (mem.eql(u8, arg, "--cache-dir")) { - if (i + 1 >= args.len) fatal("expected argument after '{}'", .{arg}); + if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); i += 1; override_local_cache_dir = args[i]; continue; } else if (mem.eql(u8, arg, "--global-cache-dir")) { - if (i + 1 >= args.len) fatal("expected argument after '{}'", .{arg}); + if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); i += 1; override_global_cache_dir = args[i]; continue; @@ -2344,7 +2344,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v } else introspect.findZigLibDirFromSelfExe(arena, self_exe_path) catch |err| { - fatal("unable to find zig installation directory: {}", .{@errorName(err)}); + fatal("unable to find zig installation directory: {s}", .{@errorName(err)}); }; defer zig_lib_directory.handle.close(); @@ -2385,7 +2385,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v } else |err| switch (err) { error.FileNotFound => { dirname = fs.path.dirname(dirname) orelse { - std.log.info("{}", .{ + std.log.info("{s}", .{ \\Initialize a 'build.zig' template file with `zig init-lib` or `zig init-exe`, \\or see `zig --help` for more options. }); @@ -2467,7 +2467,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v .self_exe_path = self_exe_path, .thread_pool = &thread_pool, }) catch |err| { - fatal("unable to create compilation: {}", .{@errorName(err)}); + fatal("unable to create compilation: {s}", .{@errorName(err)}); }; defer comp.destroy(); @@ -2493,11 +2493,11 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v .Exited => |code| { if (code == 0) return cleanExit(); const cmd = try argvCmd(arena, child_argv); - fatal("the following build command failed with exit code {}:\n{}", .{ code, cmd }); + fatal("the following build command failed with exit code {}:\n{s}", .{ code, cmd }); }, else => { const cmd = try argvCmd(arena, child_argv); - fatal("the following build command crashed:\n{}", .{cmd}); + fatal("the following build command crashed:\n{s}", .{cmd}); }, } } @@ -2564,14 +2564,14 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { i += 1; const next_arg = args[i]; color = std.meta.stringToEnum(Color, next_arg) orelse { - fatal("expected [auto|on|off] after --color, found '{}'", .{next_arg}); + fatal("expected [auto|on|off] after --color, found '{s}'", .{next_arg}); }; } else if (mem.eql(u8, arg, "--stdin")) { stdin_flag = true; } else if (mem.eql(u8, arg, "--check")) { check_flag = true; } else { - fatal("unrecognized parameter: '{}'", .{arg}); + fatal("unrecognized parameter: '{s}'", .{arg}); } } else { try input_files.append(arg); @@ -2590,7 +2590,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { defer gpa.free(source_code); const tree = std.zig.parse(gpa, source_code) catch |err| { - fatal("error parsing stdin: {}", .{err}); + fatal("error parsing stdin: {s}", .{err}); }; defer tree.deinit(); @@ -2629,7 +2629,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { for (input_files.items) |file_path| { // Get the real path here to avoid Windows failing on relative file paths with . or .. in them. const real_path = fs.realpathAlloc(gpa, file_path) catch |err| { - fatal("unable to open '{}': {}", .{ file_path, err }); + fatal("unable to open '{s}': {s}", .{ file_path, @errorName(err) }); }; defer gpa.free(real_path); @@ -2668,7 +2668,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_ fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), else => { - warn("unable to format '{}': {}", .{ file_path, err }); + warn("unable to format '{s}': {s}", .{ file_path, @errorName(err) }); fmt.any_error = true; return; }, @@ -2702,7 +2702,7 @@ fn fmtPathDir( try fmtPathDir(fmt, full_path, check_mode, dir, entry.name); } else { fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| { - warn("unable to format '{}': {}", .{ full_path, err }); + warn("unable to format '{s}': {s}", .{ full_path, @errorName(err) }); fmt.any_error = true; return; }; @@ -2761,7 +2761,7 @@ fn fmtPathFile( const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree); if (anything_changed) { const stdout = io.getStdOut().writer(); - try stdout.print("{}\n", .{file_path}); + try stdout.print("{s}\n", .{file_path}); fmt.any_error = true; } } else { @@ -2779,7 +2779,7 @@ fn fmtPathFile( try af.file.writeAll(fmt.out_buffer.items); try af.finish(); const stdout = io.getStdOut().writer(); - try stdout.print("{}\n", .{file_path}); + try stdout.print("{s}\n", .{file_path}); } } @@ -2812,7 +2812,7 @@ fn printErrMsgToFile( const text = text_buf.items; const stream = file.outStream(); - try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text }); + try stream.print("{s}:{}:{}: error: {s}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text }); if (!color_on) return; @@ -2984,7 +2984,7 @@ pub const ClangArgIterator = struct { const max_bytes = 10 * 1024 * 1024; // 10 MiB of command line arguments is a reasonable limit const resp_file_path = arg[1..]; const resp_contents = fs.cwd().readFileAlloc(allocator, resp_file_path, max_bytes) catch |err| { - fatal("unable to read response file '{}': {}", .{ resp_file_path, @errorName(err) }); + fatal("unable to read response file '{s}': {s}", .{ resp_file_path, @errorName(err) }); }; defer allocator.free(resp_contents); // TODO is there a specification for this file format? Let's find it and make this parsing more robust @@ -3057,7 +3057,7 @@ pub const ClangArgIterator = struct { const prefix_len = clang_arg.matchStartsWith(arg); if (prefix_len == arg.len) { if (self.next_index >= self.argv.len) { - fatal("Expected parameter after '{}'", .{arg}); + fatal("Expected parameter after '{s}'", .{arg}); } self.only_arg = self.argv[self.next_index]; self.incrementArgIndex(); @@ -3078,7 +3078,7 @@ pub const ClangArgIterator = struct { if (prefix_len != 0) { self.only_arg = arg[prefix_len..]; if (self.next_index >= self.argv.len) { - fatal("Expected parameter after '{}'", .{arg}); + fatal("Expected parameter after '{s}'", .{arg}); } self.second_arg = self.argv[self.next_index]; self.incrementArgIndex(); @@ -3089,7 +3089,7 @@ pub const ClangArgIterator = struct { }, .separate => if (clang_arg.matchEql(arg) > 0) { if (self.next_index >= self.argv.len) { - fatal("Expected parameter after '{}'", .{arg}); + fatal("Expected parameter after '{s}'", .{arg}); } self.only_arg = self.argv[self.next_index]; self.incrementArgIndex(); @@ -3115,7 +3115,7 @@ pub const ClangArgIterator = struct { }, } else { - fatal("Unknown Clang option: '{}'", .{arg}); + fatal("Unknown Clang option: '{s}'", .{arg}); } } @@ -3143,7 +3143,7 @@ pub const ClangArgIterator = struct { fn parseCodeModel(arg: []const u8) std.builtin.CodeModel { return std.meta.stringToEnum(std.builtin.CodeModel, arg) orelse - fatal("unsupported machine code model: '{}'", .{arg}); + fatal("unsupported machine code model: '{s}'", .{arg}); } /// Raise the open file descriptor limit. Ask and ye shall receive. @@ -3263,7 +3263,7 @@ fn detectNativeTargetInfo(gpa: *Allocator, cross_target: std.zig.CrossTarget) !s // CPU model & feature detection is todo so here we rely on LLVM. // https://github.com/ziglang/zig/issues/4591 if (!build_options.have_llvm) - fatal("CPU features detection is not yet available for {} without LLVM extensions", .{@tagName(arch)}); + fatal("CPU features detection is not yet available for {s} without LLVM extensions", .{@tagName(arch)}); const llvm = @import("llvm_bindings.zig"); const llvm_cpu_name = llvm.GetHostCPUName(); diff --git a/src/mingw.zig b/src/mingw.zig index 246b0f33dc..d55cc28b2b 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -381,7 +381,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { const term = child.wait() catch |err| { // TODO surface a proper error here - log.err("unable to spawn {}: {}", .{ args[0], @errorName(err) }); + log.err("unable to spawn {s}: {s}", .{ args[0], @errorName(err) }); return error.ClangPreprocessorFailed; }; @@ -395,7 +395,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { }, else => { // TODO surface a proper error here - log.err("clang terminated unexpectedly with stderr: {}", .{stderr}); + log.err("clang terminated unexpectedly with stderr: {s}", .{stderr}); return error.ClangPreprocessorFailed; }, } diff --git a/src/musl.zig b/src/musl.zig index f67fe90add..a865e78623 100644 --- a/src/musl.zig +++ b/src/musl.zig @@ -155,21 +155,21 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { if (!is_arch_specific) { // Look for an arch specific override. override_path.shrinkRetainingCapacity(0); - try override_path.writer().print("{}" ++ s ++ "{}" ++ s ++ "{}.s", .{ + try override_path.writer().print("{s}" ++ s ++ "{s}" ++ s ++ "{s}.s", .{ dirname, arch_name, noextbasename, }); if (source_table.contains(override_path.items)) continue; override_path.shrinkRetainingCapacity(0); - try override_path.writer().print("{}" ++ s ++ "{}" ++ s ++ "{}.S", .{ + try override_path.writer().print("{s}" ++ s ++ "{s}" ++ s ++ "{s}.S", .{ dirname, arch_name, noextbasename, }); if (source_table.contains(override_path.items)) continue; override_path.shrinkRetainingCapacity(0); - try override_path.writer().print("{}" ++ s ++ "{}" ++ s ++ "{}.c", .{ + try override_path.writer().print("{s}" ++ s ++ "{s}" ++ s ++ "{s}.c", .{ dirname, arch_name, noextbasename, }); if (source_table.contains(override_path.items)) @@ -322,7 +322,7 @@ fn add_cc_args( const target = comp.getTarget(); const arch_name = target_util.archMuslName(target.cpu.arch); const os_name = @tagName(target.os.tag); - const triple = try std.fmt.allocPrint(arena, "{}-{}-musl", .{ arch_name, os_name }); + const triple = try std.fmt.allocPrint(arena, "{s}-{s}-musl", .{ arch_name, os_name }); const o_arg = if (want_O3) "-O3" else "-Os"; try args.appendSlice(&[_][]const u8{ diff --git a/src/print_env.zig b/src/print_env.zig index 8aa692e644..bcf4a983ab 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -9,7 +9,7 @@ pub fn cmdEnv(gpa: *Allocator, args: []const []const u8, stdout: std.fs.File.Wri defer gpa.free(self_exe_path); var zig_lib_directory = introspect.findZigLibDirFromSelfExe(gpa, self_exe_path) catch |err| { - fatal("unable to find zig installation directory: {}\n", .{@errorName(err)}); + fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); }; defer gpa.free(zig_lib_directory.path.?); defer zig_lib_directory.handle.close(); diff --git a/src/print_targets.zig b/src/print_targets.zig index 724cb7a9ac..cf55eee516 100644 --- a/src/print_targets.zig +++ b/src/print_targets.zig @@ -18,7 +18,7 @@ pub fn cmdTargets( native_target: Target, ) !void { var zig_lib_directory = introspect.findZigLibDir(allocator) catch |err| { - fatal("unable to find zig installation directory: {}\n", .{@errorName(err)}); + fatal("unable to find zig installation directory: {s}\n", .{@errorName(err)}); }; defer zig_lib_directory.handle.close(); defer allocator.free(zig_lib_directory.path.?); @@ -61,7 +61,7 @@ pub fn cmdTargets( try jws.objectField("libc"); try jws.beginArray(); for (target.available_libcs) |libc| { - const tmp = try std.fmt.allocPrint(allocator, "{}-{}-{}", .{ + const tmp = try std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ @tagName(libc.arch), @tagName(libc.os), @tagName(libc.abi), }); defer allocator.free(tmp); diff --git a/src/stage1.zig b/src/stage1.zig index 1d50a71ad1..cf3a252ce8 100644 --- a/src/stage1.zig +++ b/src/stage1.zig @@ -37,14 +37,14 @@ pub export fn main(argc: c_int, argv: [*][*:0]u8) c_int { defer arena_instance.deinit(); const arena = &arena_instance.allocator; - const args = arena.alloc([]const u8, @intCast(usize, argc)) catch fatal("{}", .{"OutOfMemory"}); + const args = arena.alloc([]const u8, @intCast(usize, argc)) catch fatal("{s}", .{"OutOfMemory"}); for (args) |*arg, i| { arg.* = mem.spanZ(argv[i]); } if (std.builtin.mode == .Debug) { stage2.mainArgs(gpa, arena, args) catch unreachable; } else { - stage2.mainArgs(gpa, arena, args) catch |err| fatal("{}", .{@errorName(err)}); + stage2.mainArgs(gpa, arena, args) catch |err| fatal("{s}", .{@errorName(err)}); } return 0; } diff --git a/src/translate_c.zig b/src/translate_c.zig index 1b2aa4b219..c609597770 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -136,7 +136,7 @@ const Scope = struct { var proposed_name = name_copy; while (scope.contains(proposed_name)) { scope.mangle_count += 1; - proposed_name = try std.fmt.allocPrint(c.arena, "{}_{}", .{ name, scope.mangle_count }); + proposed_name = try std.fmt.allocPrint(c.arena, "{s}_{}", .{ name, scope.mangle_count }); } try scope.variables.append(.{ .name = name_copy, .alias = proposed_name }); return proposed_name; @@ -290,7 +290,7 @@ pub const Context = struct { const line = c.source_manager.getSpellingLineNumber(spelling_loc); const column = c.source_manager.getSpellingColumnNumber(spelling_loc); - return std.fmt.allocPrint(c.arena, "{}:{}:{}", .{ filename, line, column }); + return std.fmt.allocPrint(c.arena, "{s}:{d}:{d}", .{ filename, line, column }); } fn createCall(c: *Context, fn_expr: *ast.Node, params_len: ast.NodeIndex) !*ast.Node.Call { @@ -530,7 +530,7 @@ fn declVisitor(c: *Context, decl: *const clang.Decl) Error!void { }, else => { const decl_name = try c.str(decl.getDeclKindName()); - try emitWarning(c, decl.getLocation(), "ignoring {} declaration", .{decl_name}); + try emitWarning(c, decl.getLocation(), "ignoring {s} declaration", .{decl_name}); }, } } @@ -625,7 +625,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void { const param_name = if (param.name_token) |name_tok| tokenSlice(c, name_tok) else - return failDecl(c, fn_decl_loc, fn_name, "function {} parameter has no name", .{fn_name}); + return failDecl(c, fn_decl_loc, fn_name, "function {s} parameter has no name", .{fn_name}); const c_param = fn_decl.getParamDecl(param_id); const qual_type = c_param.getOriginalType(); @@ -634,7 +634,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void { const mangled_param_name = try block_scope.makeMangledName(c, param_name); if (!is_const) { - const bare_arg_name = try std.fmt.allocPrint(c.arena, "arg_{}", .{mangled_param_name}); + const bare_arg_name = try std.fmt.allocPrint(c.arena, "arg_{s}", .{mangled_param_name}); const arg_name = try block_scope.makeMangledName(c, bare_arg_name); const mut_tok = try appendToken(c, .Keyword_var, "var"); @@ -727,7 +727,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co // TODO https://github.com/ziglang/zig/issues/3756 // TODO https://github.com/ziglang/zig/issues/1802 - const checked_name = if (isZigPrimitiveType(var_name)) try std.fmt.allocPrint(c.arena, "{}_{}", .{ var_name, c.getMangle() }) else var_name; + const checked_name = if (isZigPrimitiveType(var_name)) try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ var_name, c.getMangle() }) else var_name; const var_decl_loc = var_decl.getLocation(); const qual_type = var_decl.getTypeSourceInfo_getType(); @@ -808,7 +808,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co _ = try appendToken(rp.c, .LParen, "("); const expr = try transCreateNodeStringLiteral( rp.c, - try std.fmt.allocPrint(rp.c.arena, "\"{}\"", .{str_ptr[0..str_len]}), + try std.fmt.allocPrint(rp.c.arena, "\"{s}\"", .{str_ptr[0..str_len]}), ); _ = try appendToken(rp.c, .RParen, ")"); @@ -887,7 +887,7 @@ fn transTypeDef(c: *Context, typedef_decl: *const clang.TypedefNameDecl, top_lev // TODO https://github.com/ziglang/zig/issues/3756 // TODO https://github.com/ziglang/zig/issues/1802 - const checked_name = if (isZigPrimitiveType(typedef_name)) try std.fmt.allocPrint(c.arena, "{}_{}", .{ typedef_name, c.getMangle() }) else typedef_name; + const checked_name = if (isZigPrimitiveType(typedef_name)) try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ typedef_name, c.getMangle() }) else typedef_name; if (checkForBuiltinTypedef(checked_name)) |builtin| { return transTypeDefAsBuiltin(c, typedef_decl, builtin); } @@ -958,11 +958,11 @@ fn transRecordDecl(c: *Context, record_decl: *const clang.RecordDecl) Error!?*as container_kind_name = "struct"; container_kind = .Keyword_struct; } else { - try emitWarning(c, record_loc, "record {} is not a struct or union", .{bare_name}); + try emitWarning(c, record_loc, "record {s} is not a struct or union", .{bare_name}); return null; } - const name = try std.fmt.allocPrint(c.arena, "{}_{}", .{ container_kind_name, bare_name }); + const name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_kind_name, bare_name }); _ = try c.decl_table.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), name); const visib_tok = if (!is_unnamed) try appendToken(c, .Keyword_pub, "pub") else null; @@ -1003,7 +1003,7 @@ fn transRecordDecl(c: *Context, record_decl: *const clang.RecordDecl) Error!?*as _ = try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {}); const opaque_type = try transCreateNodeOpaqueType(c); semicolon = try appendToken(c, .Semicolon, ";"); - try emitWarning(c, field_loc, "{} demoted to opaque type - has bitfield", .{container_kind_name}); + try emitWarning(c, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name}); break :blk opaque_type; } @@ -1011,7 +1011,7 @@ fn transRecordDecl(c: *Context, record_decl: *const clang.RecordDecl) Error!?*as _ = try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {}); const opaque_type = try transCreateNodeOpaqueType(c); semicolon = try appendToken(c, .Semicolon, ";"); - try emitWarning(c, field_loc, "{} demoted to opaque type - has variable length array", .{container_kind_name}); + try emitWarning(c, field_loc, "{s} demoted to opaque type - has variable length array", .{container_kind_name}); break :blk opaque_type; } @@ -1030,7 +1030,7 @@ fn transRecordDecl(c: *Context, record_decl: *const clang.RecordDecl) Error!?*as _ = try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {}); const opaque_type = try transCreateNodeOpaqueType(c); semicolon = try appendToken(c, .Semicolon, ";"); - try emitWarning(c, record_loc, "{} demoted to opaque type - unable to translate type of field {}", .{ container_kind_name, raw_name }); + try emitWarning(c, record_loc, "{s} demoted to opaque type - unable to translate type of field {s}", .{ container_kind_name, raw_name }); break :blk opaque_type; }, else => |e| return e, @@ -1114,7 +1114,7 @@ fn transEnumDecl(c: *Context, enum_decl: *const clang.EnumDecl) Error!?*ast.Node is_unnamed = true; } - const name = try std.fmt.allocPrint(c.arena, "enum_{}", .{bare_name}); + const name = try std.fmt.allocPrint(c.arena, "enum_{s}", .{bare_name}); _ = try c.decl_table.put(c.gpa, @ptrToInt(enum_decl.getCanonicalDecl()), name); const visib_tok = if (!is_unnamed) try appendToken(c, .Keyword_pub, "pub") else null; @@ -1385,7 +1385,7 @@ fn transStmt( rp, error.UnsupportedTranslation, stmt.getBeginLoc(), - "TODO implement translation of stmt class {}", + "TODO implement translation of stmt class {s}", .{@tagName(sc)}, ); }, @@ -1684,7 +1684,7 @@ fn transDeclStmtOne( rp, error.UnsupportedTranslation, decl.getLocation(), - "TODO implement translation of DeclStmt kind {}", + "TODO implement translation of DeclStmt kind {s}", .{@tagName(kind)}, ), } @@ -1782,7 +1782,7 @@ fn transImplicitCastExpr( rp, error.UnsupportedTranslation, @ptrCast(*const clang.Stmt, expr).getBeginLoc(), - "TODO implement translation of CastKind {}", + "TODO implement translation of CastKind {s}", .{@tagName(kind)}, ), } @@ -2043,7 +2043,7 @@ fn transStringLiteral( rp, error.UnsupportedTranslation, @ptrCast(*const clang.Stmt, stmt).getBeginLoc(), - "TODO: support string literal kind {}", + "TODO: support string literal kind {s}", .{kind}, ), } @@ -2168,7 +2168,6 @@ fn transCCast( // @boolToInt returns either a comptime_int or a u1 // TODO: if dst_type is 1 bit & signed (bitfield) we need @bitCast // instead of @as - const builtin_node = try rp.c.createBuiltinCall("@boolToInt", 1); builtin_node.params()[0] = expr; builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); @@ -2455,7 +2454,7 @@ fn transInitListExpr( ); } else { const type_name = rp.c.str(qual_type.getTypeClassName()); - return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported initlist type: '{}'", .{type_name}); + return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported initlist type: '{s}'", .{type_name}); } } @@ -4433,7 +4432,8 @@ fn transCreateNodeBoolLiteral(c: *Context, value: bool) !*ast.Node { } fn transCreateNodeInt(c: *Context, int: anytype) !*ast.Node { - const token = try appendTokenFmt(c, .IntegerLiteral, "{}", .{int}); + const fmt_s = if (comptime std.meta.trait.isIntegerNumber(@TypeOf(int))) "{d}" else "{s}"; + const token = try appendTokenFmt(c, .IntegerLiteral, fmt_s, .{int}); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .IntegerLiteral }, @@ -4442,8 +4442,8 @@ fn transCreateNodeInt(c: *Context, int: anytype) !*ast.Node { return &node.base; } -fn transCreateNodeFloat(c: *Context, int: anytype) !*ast.Node { - const token = try appendTokenFmt(c, .FloatLiteral, "{}", .{int}); +fn transCreateNodeFloat(c: *Context, str: []const u8) !*ast.Node { + const token = try appendTokenFmt(c, .FloatLiteral, "{s}", .{str}); const node = try c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .FloatLiteral }, @@ -4916,7 +4916,7 @@ fn transType(rp: RestorePoint, ty: *const clang.Type, source_loc: clang.SourceLo }, else => { const type_name = rp.c.str(ty.getTypeClassName()); - return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported type: '{}'", .{type_name}); + return revertAndWarn(rp, error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{type_name}); }, } } @@ -4999,7 +4999,7 @@ fn transCC( rp, error.UnsupportedType, source_loc, - "unsupported calling convention: {}", + "unsupported calling convention: {s}", .{@tagName(clang_cc)}, ), } @@ -5117,7 +5117,7 @@ fn finishTransFnProto( _ = try appendToken(rp.c, .LParen, "("); const expr = try transCreateNodeStringLiteral( rp.c, - try std.fmt.allocPrint(rp.c.arena, "\"{}\"", .{str_ptr[0..str_len]}), + try std.fmt.allocPrint(rp.c.arena, "\"{s}\"", .{str_ptr[0..str_len]}), ); _ = try appendToken(rp.c, .RParen, ")"); @@ -5214,7 +5214,7 @@ fn revertAndWarn( fn emitWarning(c: *Context, loc: clang.SourceLocation, comptime format: []const u8, args: anytype) !void { const args_prefix = .{c.locStr(loc)}; - _ = try appendTokenFmt(c, .LineComment, "// {}: warning: " ++ format, args_prefix ++ args); + _ = try appendTokenFmt(c, .LineComment, "// {s}: warning: " ++ format, args_prefix ++ args); } pub fn failDecl(c: *Context, loc: clang.SourceLocation, name: []const u8, comptime format: []const u8, args: anytype) !void { @@ -5228,7 +5228,7 @@ pub fn failDecl(c: *Context, loc: clang.SourceLocation, name: []const u8, compti const msg_tok = try appendTokenFmt(c, .StringLiteral, "\"" ++ format ++ "\"", args); const rparen_tok = try appendToken(c, .RParen, ")"); const semi_tok = try appendToken(c, .Semicolon, ";"); - _ = try appendTokenFmt(c, .LineComment, "// {}", .{c.locStr(loc)}); + _ = try appendTokenFmt(c, .LineComment, "// {s}", .{c.locStr(loc)}); const msg_node = try c.arena.create(ast.Node.OneToken); msg_node.* = .{ @@ -5258,7 +5258,7 @@ pub fn failDecl(c: *Context, loc: clang.SourceLocation, name: []const u8, compti fn appendToken(c: *Context, token_id: Token.Id, bytes: []const u8) !ast.TokenIndex { std.debug.assert(token_id != .Identifier); // use appendIdentifier - return appendTokenFmt(c, token_id, "{}", .{bytes}); + return appendTokenFmt(c, token_id, "{s}", .{bytes}); } fn appendTokenFmt(c: *Context, token_id: Token.Id, comptime format: []const u8, args: anytype) !ast.TokenIndex { @@ -5329,7 +5329,7 @@ fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node { } fn transCreateNodeIdentifierUnchecked(c: *Context, name: []const u8) !*ast.Node { - const token_index = try appendTokenFmt(c, .Identifier, "{}", .{name}); + const token_index = try appendTokenFmt(c, .Identifier, "{s}", .{name}); const identifier = try c.arena.create(ast.Node.OneToken); identifier.* = .{ .base = .{ .tag = .Identifier }, @@ -5390,7 +5390,7 @@ fn transPreprocessorEntities(c: *Context, unit: *clang.ASTUnit) Error!void { const name = try c.str(raw_name); // TODO https://github.com/ziglang/zig/issues/3756 // TODO https://github.com/ziglang/zig/issues/1802 - const mangled_name = if (isZigPrimitiveType(name)) try std.fmt.allocPrint(c.arena, "{}_{}", .{ name, c.getMangle() }) else name; + const mangled_name = if (isZigPrimitiveType(name)) try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ name, c.getMangle() }) else name; if (scope.containsNow(mangled_name)) { continue; } @@ -5468,7 +5468,7 @@ fn transMacroDefine(c: *Context, m: *MacroCtx) ParseError!void { const init_node = try parseCExpr(c, m, scope); const last = m.next().?; if (last != .Eof and last != .Nl) - return m.fail(c, "unable to translate C expr: unexpected token .{}", .{@tagName(last)}); + return m.fail(c, "unable to translate C expr: unexpected token .{s}", .{@tagName(last)}); const semicolon_token = try appendToken(c, .Semicolon, ";"); const node = try ast.Node.VarDecl.create(c.arena, .{ @@ -5540,7 +5540,7 @@ fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void { const expr = try parseCExpr(c, m, scope); const last = m.next().?; if (last != .Eof and last != .Nl) - return m.fail(c, "unable to translate C expr: unexpected token .{}", .{@tagName(last)}); + return m.fail(c, "unable to translate C expr: unexpected token .{s}", .{@tagName(last)}); _ = try appendToken(c, .Semicolon, ";"); const type_of_arg = if (!expr.tag.isBlock()) expr else blk: { const stmts = expr.blockStatements(); @@ -5623,11 +5623,11 @@ fn parseCNumLit(c: *Context, m: *MacroCtx) ParseError!*ast.Node { switch (lit_bytes[1]) { '0'...'7' => { // Octal - lit_bytes = try std.fmt.allocPrint(c.arena, "0o{}", .{lit_bytes}); + lit_bytes = try std.fmt.allocPrint(c.arena, "0o{s}", .{lit_bytes}); }, 'X' => { // Hexadecimal with capital X, valid in C but not in Zig - lit_bytes = try std.fmt.allocPrint(c.arena, "0x{}", .{lit_bytes[2..]}); + lit_bytes = try std.fmt.allocPrint(c.arena, "0x{s}", .{lit_bytes[2..]}); }, else => {}, } @@ -5659,7 +5659,7 @@ fn parseCNumLit(c: *Context, m: *MacroCtx) ParseError!*ast.Node { }, .FloatLiteral => |suffix| { if (lit_bytes[0] == '.') - lit_bytes = try std.fmt.allocPrint(c.arena, "0{}", .{lit_bytes}); + lit_bytes = try std.fmt.allocPrint(c.arena, "0{s}", .{lit_bytes}); if (suffix == .none) { return transCreateNodeFloat(c, lit_bytes); } @@ -5937,7 +5937,7 @@ fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!* const next_id = m.next().?; if (next_id != .RParen) { - try m.fail(c, "unable to translate C expr: expected ')' instead got: {}", .{@tagName(next_id)}); + try m.fail(c, "unable to translate C expr: expected ')' instead got: {s}", .{@tagName(next_id)}); return error.ParseError; } var saw_l_paren = false; @@ -5995,7 +5995,7 @@ fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!* return &group_node.base; }, else => { - try m.fail(c, "unable to translate C expr: unexpected token .{}", .{@tagName(tok)}); + try m.fail(c, "unable to translate C expr: unexpected token .{s}", .{@tagName(tok)}); return error.ParseError; }, } diff --git a/src/value.zig b/src/value.zig index 8180df2f9e..10f58fa44f 100644 --- a/src/value.zig +++ b/src/value.zig @@ -464,7 +464,7 @@ pub const Value = extern union { .ty => return val.castTag(.ty).?.data.format("", options, out_stream), .int_type => { const int_type = val.castTag(.int_type).?.data; - return out_stream.print("{}{}", .{ + return out_stream.print("{s}{d}", .{ if (int_type.signed) "s" else "u", int_type.bits, }); @@ -507,7 +507,7 @@ pub const Value = extern union { } return out_stream.writeAll("}"); }, - .@"error" => return out_stream.print("error.{}", .{val.castTag(.@"error").?.data.name}), + .@"error" => return out_stream.print("error.{s}", .{val.castTag(.@"error").?.data.name}), .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"), }; } diff --git a/src/zir.zig b/src/zir.zig index ce8498d7e8..94e2b24b0c 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1150,7 +1150,7 @@ pub const Module = struct { for (self.decls) |decl, i| { write.next_instr_index = 0; - try stream.print("@{} ", .{decl.name}); + try stream.print("@{s} ", .{decl.name}); try write.writeInstToStream(stream, decl.inst); try stream.writeByte('\n'); } @@ -1206,13 +1206,13 @@ const Writer = struct { if (@typeInfo(arg_field.field_type) == .Optional) { if (@field(inst.kw_args, arg_field.name)) |non_optional| { if (need_comma) try stream.writeAll(", "); - try stream.print("{}=", .{arg_field.name}); + try stream.print("{s}=", .{arg_field.name}); try self.writeParamToStream(stream, &non_optional); need_comma = true; } } else { if (need_comma) try stream.writeAll(", "); - try stream.print("{}=", .{arg_field.name}); + try stream.print("{s}=", .{arg_field.name}); try self.writeParamToStream(stream, &@field(inst.kw_args, arg_field.name)); need_comma = true; } @@ -1334,16 +1334,16 @@ const Writer = struct { if (info.index) |i| { try stream.print("%{}", .{info.index}); } else { - try stream.print("@{}", .{info.name}); + try stream.print("@{s}", .{info.name}); } } else if (inst.cast(Inst.DeclVal)) |decl_val| { - try stream.print("@{}", .{decl_val.positionals.name}); + try stream.print("@{s}", .{decl_val.positionals.name}); } else if (inst.cast(Inst.DeclValInModule)) |decl_val| { - try stream.print("@{}", .{decl_val.positionals.decl.name}); + try stream.print("@{s}", .{decl_val.positionals.decl.name}); } else { // This should be unreachable in theory, but since ZIR is used for debugging the compiler // we output some debug text instead. - try stream.print("?{}?", .{@tagName(inst.tag)}); + try stream.print("?{s}?", .{@tagName(inst.tag)}); } } }; @@ -1424,7 +1424,7 @@ const Parser = struct { const decl = try parseInstruction(self, &body_context, ident); const ident_index = body_context.instructions.items.len; if (try body_context.name_map.fetchPut(ident, decl.inst)) |_| { - return self.fail("redefinition of identifier '{}'", .{ident}); + return self.fail("redefinition of identifier '{s}'", .{ident}); } try body_context.instructions.append(decl.inst); continue; @@ -1510,7 +1510,7 @@ const Parser = struct { const decl = try parseInstruction(self, null, ident); const ident_index = self.decls.items.len; if (try self.global_name_map.fetchPut(ident, decl.inst)) |_| { - return self.fail("redefinition of identifier '{}'", .{ident}); + return self.fail("redefinition of identifier '{s}'", .{ident}); } try self.decls.append(self.allocator, decl); }, @@ -1538,7 +1538,7 @@ const Parser = struct { for (bytes) |byte| { if (self.source[self.i] != byte) { self.i = start; - return self.fail("expected '{}'", .{bytes}); + return self.fail("expected '{s}'", .{bytes}); } self.i += 1; } @@ -1585,7 +1585,7 @@ const Parser = struct { return parseInstructionGeneric(self, field.name, tag.Type(), tag, body_ctx, name, contents_start); } } - return self.fail("unknown instruction '{}'", .{fn_name}); + return self.fail("unknown instruction '{s}'", .{fn_name}); } fn parseInstructionGeneric( @@ -1621,7 +1621,7 @@ const Parser = struct { self.i += 1; skipSpace(self); } else if (self.source[self.i] == ')') { - return self.fail("expected positional parameter '{}'", .{arg_field.name}); + return self.fail("expected positional parameter '{s}'", .{arg_field.name}); } @field(inst_specific.positionals, arg_field.name) = try parseParameterGeneric( self, @@ -1648,7 +1648,7 @@ const Parser = struct { break; } } else { - return self.fail("unrecognized keyword parameter: '{}'", .{name}); + return self.fail("unrecognized keyword parameter: '{s}'", .{name}); } skipSpace(self); } @@ -1672,7 +1672,7 @@ const Parser = struct { ' ', '\n', ',', ')' => { const enum_name = self.source[start..self.i]; return std.meta.stringToEnum(T, enum_name) orelse { - return self.fail("tag '{}' not a member of enum '{}'", .{ enum_name, @typeName(T) }); + return self.fail("tag '{s}' not a member of enum '{s}'", .{ enum_name, @typeName(T) }); }; }, 0 => return self.failByte(0), @@ -1710,7 +1710,7 @@ const Parser = struct { BigIntConst => return self.parseIntegerLiteral(), usize => { const big_int = try self.parseIntegerLiteral(); - return big_int.to(usize) catch |err| return self.fail("integer literal: {}", .{@errorName(err)}); + return big_int.to(usize) catch |err| return self.fail("integer literal: {s}", .{@errorName(err)}); }, TypedValue => return self.fail("'const' is a special instruction; not legal in ZIR text", .{}), *IrModule.Decl => return self.fail("'declval_in_module' is a special instruction; not legal in ZIR text", .{}), @@ -1759,7 +1759,7 @@ const Parser = struct { }, else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), } - return self.fail("TODO parse parameter {}", .{@typeName(T)}); + return self.fail("TODO parse parameter {s}", .{@typeName(T)}); } fn parseParameterInst(self: *Parser, body_ctx: ?*Body) !*Inst { @@ -1788,7 +1788,7 @@ const Parser = struct { const src = name_start - 1; if (local_ref) { self.i = src; - return self.fail("unrecognized identifier: {}", .{bad_name}); + return self.fail("unrecognized identifier: {s}", .{bad_name}); } else { const declval = try self.arena.allocator.create(Inst.DeclVal); declval.* = .{ @@ -1873,7 +1873,7 @@ pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void { const fn_ty = module_fn.owner_decl.typed_value.most_recent.typed_value.ty; _ = ctx.emitFn(module_fn, 0, fn_ty) catch |err| { - std.debug.print("unable to dump function: {}\n", .{err}); + std.debug.print("unable to dump function: {s}\n", .{@errorName(err)}); return; }; var module = Module{ @@ -2203,7 +2203,7 @@ const EmitZIR = struct { }; return self.emitStringLiteral(src, bytes); }, - else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {}", .{@tagName(t)}), + else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {s}", .{@tagName(t)}), } }, .ComptimeInt => return self.emitComptimeIntVal(src, typed_value.val), @@ -2274,7 +2274,7 @@ const EmitZIR = struct { }; return self.emitUnnamedDecl(&inst.base); }, - else => |t| std.debug.panic("TODO implement emitTypedValue for {}", .{@tagName(t)}), + else => |t| std.debug.panic("TODO implement emitTypedValue for {s}", .{@tagName(t)}), } } @@ -2947,7 +2947,7 @@ pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8 try write.inst_table.ensureCapacity(@intCast(u32, instructions.len)); const stderr = std.io.getStdErr().outStream(); - try stderr.print("{} {s} {{ // unanalyzed\n", .{ kind, decl_name }); + try stderr.print("{s} {s} {{ // unanalyzed\n", .{ kind, decl_name }); for (instructions) |inst| { const my_i = write.next_instr_index; @@ -2967,5 +2967,5 @@ pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8 try stderr.writeByte('\n'); } - try stderr.print("}} // {} {s}\n\n", .{ kind, decl_name }); + try stderr.print("}} // {s} {s}\n\n", .{ kind, decl_name }); } diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 629dfa0c9a..72a1b04238 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -274,7 +274,7 @@ pub fn resolveInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: { const decl_name = declval.positionals.name; const entry = zir_module.contents.module.findDecl(decl_name) orelse - return mod.fail(scope, old_inst.src, "decl '{}' not found", .{decl_name}); + return mod.fail(scope, old_inst.src, "decl '{s}' not found", .{decl_name}); break :blk entry; } else blk: { // If this assert trips, the instruction that was referenced did not get @@ -564,14 +564,14 @@ fn analyzeInstStr(mod: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerErr fn analyzeInstExport(mod: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst { const symbol_name = try resolveConstString(mod, scope, export_inst.positionals.symbol_name); const exported_decl = mod.lookupDeclName(scope, export_inst.positionals.decl_name) orelse - return mod.fail(scope, export_inst.base.src, "decl '{}' not found", .{export_inst.positionals.decl_name}); + return mod.fail(scope, export_inst.base.src, "decl '{s}' not found", .{export_inst.positionals.decl_name}); try mod.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl); return mod.constVoid(scope, export_inst.base.src); } fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const msg = try resolveConstString(mod, scope, inst.positionals.operand); - return mod.fail(scope, inst.base.src, "{}", .{msg}); + return mod.fail(scope, inst.base.src, "{s}", .{msg}); } fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst { @@ -918,7 +918,7 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In for (inst.positionals.fields) |field_name| { const entry = try mod.getErrorValue(field_name); if (payload.data.fields.fetchPutAssumeCapacity(entry.key, entry.value)) |prev| { - return mod.fail(scope, inst.base.src, "duplicate error: '{}'", .{field_name}); + return mod.fail(scope, inst.base.src, "duplicate error: '{s}'", .{field_name}); } } // TODO create name in format "error:line:column" @@ -1068,7 +1068,7 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr return mod.fail( scope, fieldptr.positionals.field_name.src, - "no member named '{}' in '{}'", + "no member named '{s}' in '{}'", .{ field_name, elem_ty }, ); } @@ -1089,7 +1089,7 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr return mod.fail( scope, fieldptr.positionals.field_name.src, - "no member named '{}' in '{}'", + "no member named '{s}' in '{}'", .{ field_name, elem_ty }, ); } @@ -1107,7 +1107,7 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr // TODO resolve inferred error sets const entry = if (val.castTag(.error_set)) |payload| (payload.data.fields.getEntry(field_name) orelse - return mod.fail(scope, fieldptr.base.src, "no error named '{}' in '{}'", .{ field_name, child_type })).* + return mod.fail(scope, fieldptr.base.src, "no error named '{s}' in '{}'", .{ field_name, child_type })).* else try mod.getErrorValue(field_name); @@ -1135,9 +1135,9 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr } if (&container_scope.file_scope.base == mod.root_scope) { - return mod.fail(scope, fieldptr.base.src, "root source file has no member called '{}'", .{field_name}); + return mod.fail(scope, fieldptr.base.src, "root source file has no member called '{s}'", .{field_name}); } else { - return mod.fail(scope, fieldptr.base.src, "container '{}' has no member called '{}'", .{ child_type, field_name }); + return mod.fail(scope, fieldptr.base.src, "container '{}' has no member called '{s}'", .{ child_type, field_name }); } }, else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{child_type}), @@ -1503,14 +1503,14 @@ fn analyzeInstImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErr const file_scope = mod.analyzeImport(scope, inst.base.src, operand) catch |err| switch (err) { error.ImportOutsidePkgPath => { - return mod.fail(scope, inst.base.src, "import of file outside package path: '{}'", .{operand}); + return mod.fail(scope, inst.base.src, "import of file outside package path: '{s}'", .{operand}); }, error.FileNotFound => { - return mod.fail(scope, inst.base.src, "unable to find '{}'", .{operand}); + return mod.fail(scope, inst.base.src, "unable to find '{s}'", .{operand}); }, else => { // TODO user friendly error to string - return mod.fail(scope, inst.base.src, "unable to open '{}': {}", .{ operand, @errorName(err) }); + return mod.fail(scope, inst.base.src, "unable to open '{s}': {s}", .{ operand, @errorName(err) }); }, }; return mod.constType(scope, inst.base.src, file_scope.root_container.ty); @@ -1637,7 +1637,7 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; if (!is_int and !(is_float and floatOpAllowed(inst.base.tag))) { - return mod.fail(scope, inst.base.src, "invalid operands to binary expression: '{}' and '{}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) }); + return mod.fail(scope, inst.base.src, "invalid operands to binary expression: '{s}' and '{s}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) }); } if (casted_lhs.value()) |lhs_val| { @@ -1656,7 +1656,7 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn const ir_tag = switch (inst.base.tag) { .add => Inst.Tag.add, .sub => Inst.Tag.sub, - else => return mod.fail(scope, inst.base.src, "TODO implement arithmetic for operand '{}''", .{@tagName(inst.base.tag)}), + else => return mod.fail(scope, inst.base.src, "TODO implement arithmetic for operand '{s}''", .{@tagName(inst.base.tag)}), }; return mod.addBinOp(b, inst.base.src, scalar_type, ir_tag, casted_lhs, casted_rhs); @@ -1689,7 +1689,7 @@ fn analyzeInstComptimeOp(mod: *Module, scope: *Scope, res_type: Type, inst: *zir mod.floatSub(scope, res_type, inst.base.src, lhs_val, rhs_val); break :blk val; }, - else => return mod.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{}'", .{@tagName(inst.base.tag)}), + else => return mod.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{s}'", .{@tagName(inst.base.tag)}), }; return mod.constInst(scope, inst.base.src, .{ @@ -1781,7 +1781,7 @@ fn analyzeInstCmp( return mod.fail(scope, inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { if (!is_equality_cmp) { - return mod.fail(scope, inst.base.src, "{} operator not allowed for errors", .{@tagName(op)}); + return mod.fail(scope, inst.base.src, "{s} operator not allowed for errors", .{@tagName(op)}); } return mod.fail(scope, inst.base.src, "TODO implement equality comparison between errors", .{}); } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) { @@ -1962,7 +1962,7 @@ fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerErr const decl_name = inst.positionals.name; const zir_module = scope.namespace().cast(Scope.ZIRModule).?; const src_decl = zir_module.contents.module.findDecl(decl_name) orelse - return mod.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name}); + return mod.fail(scope, inst.base.src, "use of undeclared identifier '{s}'", .{decl_name}); const decl = try resolveCompleteZirDecl(mod, scope, src_decl.decl); -- cgit v1.2.3 From 5b981b1be7b387a3f51d60b8642064e6642b956c Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sat, 2 Jan 2021 12:29:37 +0100 Subject: Remove some unwanted changes Leftovers after a long rebase. --- build.zig | 189 ----------------------- lib/std/array_list_sentineled.zig | 229 ---------------------------- lib/std/progress.zig | 310 -------------------------------------- src/codegen/c.zig | 5 +- src/codegen/llvm.zig | 125 --------------- src/link/C.zig | 3 - src/llvm_backend.zig | 2 +- 7 files changed, 3 insertions(+), 860 deletions(-) delete mode 100644 lib/std/array_list_sentineled.zig delete mode 100644 lib/std/progress.zig delete mode 100644 src/codegen/llvm.zig (limited to 'src/codegen') diff --git a/build.zig b/build.zig index ab230d8f46..2b0685d19e 100644 --- a/build.zig +++ b/build.zig @@ -359,195 +359,6 @@ pub fn build(b: *Builder) !void { test_step.dependOn(docs_step); } -fn dependOnLib(b: *Builder, lib_exe_obj: anytype, dep: LibraryDep) void { - for (dep.libdirs.items) |lib_dir| { - lib_exe_obj.addLibPath(lib_dir); - } - const lib_dir = fs.path.join( - b.allocator, - &[_][]const u8{ dep.prefix, "lib" }, - ) catch unreachable; - for (dep.system_libs.items) |lib| { - const static_bare_name = if (mem.eql(u8, lib, "curses")) - @as([]const u8, "libncurses.a") - else - b.fmt("lib{s}.a", .{lib}); - const static_lib_name = fs.path.join( - b.allocator, - &[_][]const u8{ lib_dir, static_bare_name }, - ) catch unreachable; - const have_static = fileExists(static_lib_name) catch unreachable; - if (have_static) { - lib_exe_obj.addObjectFile(static_lib_name); - } else { - lib_exe_obj.linkSystemLibrary(lib); - } - } - for (dep.libs.items) |lib| { - lib_exe_obj.addObjectFile(lib); - } - for (dep.includes.items) |include_path| { - lib_exe_obj.addIncludeDir(include_path); - } -} - -fn fileExists(filename: []const u8) !bool { - fs.cwd().access(filename, .{}) catch |err| switch (err) { - error.FileNotFound => return false, - else => return err, - }; - return true; -} - -fn addCppLib(b: *Builder, lib_exe_obj: anytype, cmake_binary_dir: []const u8, lib_name: []const u8) void { - lib_exe_obj.addObjectFile(fs.path.join(b.allocator, &[_][]const u8{ - cmake_binary_dir, - "zigcpp", - b.fmt("{s}{s}{s}", .{ lib_exe_obj.target.libPrefix(), lib_name, lib_exe_obj.target.staticLibSuffix() }), - }) catch unreachable); -} - -const LibraryDep = struct { - prefix: []const u8, - libdirs: ArrayList([]const u8), - libs: ArrayList([]const u8), - system_libs: ArrayList([]const u8), - includes: ArrayList([]const u8), -}; - -fn findLLVM(b: *Builder, llvm_config_exe: []const u8) !LibraryDep { - const shared_mode = try b.exec(&[_][]const u8{ llvm_config_exe, "--shared-mode" }); - const is_static = mem.startsWith(u8, shared_mode, "static"); - const libs_output = if (is_static) - try b.exec(&[_][]const u8{ - llvm_config_exe, - "--libfiles", - "--system-libs", - }) - else - try b.exec(&[_][]const u8{ - llvm_config_exe, - "--libs", - }); - const includes_output = try b.exec(&[_][]const u8{ llvm_config_exe, "--includedir" }); - const libdir_output = try b.exec(&[_][]const u8{ llvm_config_exe, "--libdir" }); - const prefix_output = try b.exec(&[_][]const u8{ llvm_config_exe, "--prefix" }); - - var result = LibraryDep{ - .prefix = mem.tokenize(prefix_output, " \r\n").next().?, - .libs = ArrayList([]const u8).init(b.allocator), - .system_libs = ArrayList([]const u8).init(b.allocator), - .includes = ArrayList([]const u8).init(b.allocator), - .libdirs = ArrayList([]const u8).init(b.allocator), - }; - { - var it = mem.tokenize(libs_output, " \r\n"); - while (it.next()) |lib_arg| { - if (mem.startsWith(u8, lib_arg, "-l")) { - try result.system_libs.append(lib_arg[2..]); - } else { - if (fs.path.isAbsolute(lib_arg)) { - try result.libs.append(lib_arg); - } else { - var lib_arg_copy = lib_arg; - if (mem.endsWith(u8, lib_arg, ".lib")) { - lib_arg_copy = lib_arg[0 .. lib_arg.len - 4]; - } - try result.system_libs.append(lib_arg_copy); - } - } - } - } - { - var it = mem.tokenize(includes_output, " \r\n"); - while (it.next()) |include_arg| { - if (mem.startsWith(u8, include_arg, "-I")) { - try result.includes.append(include_arg[2..]); - } else { - try result.includes.append(include_arg); - } - } - } - { - var it = mem.tokenize(libdir_output, " \r\n"); - while (it.next()) |libdir| { - if (mem.startsWith(u8, libdir, "-L")) { - try result.libdirs.append(libdir[2..]); - } else { - try result.libdirs.append(libdir); - } - } - } - return result; -} - -fn configureStage2(b: *Builder, exe: anytype, ctx: Context, need_cpp_includes: bool) !void { - exe.addIncludeDir("src"); - exe.addIncludeDir(ctx.cmake_binary_dir); - addCppLib(b, exe, ctx.cmake_binary_dir, "zigcpp"); - assert(ctx.lld_include_dir.len != 0); - exe.addIncludeDir(ctx.lld_include_dir); - { - var it = mem.tokenize(ctx.lld_libraries, ";"); - while (it.next()) |lib| { - exe.addObjectFile(lib); - } - } - { - var it = mem.tokenize(ctx.clang_libraries, ";"); - while (it.next()) |lib| { - exe.addObjectFile(lib); - } - } - dependOnLib(b, exe, ctx.llvm); - - // Boy, it sure would be nice to simply linkSystemLibrary("c++") and rely on zig's - // ability to provide libc++ right? Well thanks to C++ not having a stable ABI this - // will cause linker errors. It would work in the situation when `zig cc` is used to - // build LLVM, Clang, and LLD, however when depending on them as system libraries, system - // libc++ must be used. - const cross_compile = false; // TODO - if (cross_compile) { - // In this case we assume that zig cc was used to build the LLVM, Clang, LLD dependencies. - exe.linkSystemLibrary("c++"); - } else { - if (exe.target.getOsTag() == .linux) { - // First we try to static link against gcc libstdc++. If that doesn't work, - // we fall back to -lc++ and cross our fingers. - addCxxKnownPath(b, ctx, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) { - error.RequiredLibraryNotFound => { - exe.linkSystemLibrary("c++"); - }, - else => |e| return e, - }; - - exe.linkSystemLibrary("pthread"); - } else if (exe.target.isFreeBSD()) { - try addCxxKnownPath(b, ctx, exe, "libc++.a", null, need_cpp_includes); - exe.linkSystemLibrary("pthread"); - } else if (exe.target.isDarwin()) { - if (addCxxKnownPath(b, ctx, exe, "libgcc_eh.a", "", need_cpp_includes)) { - // Compiler is GCC. - try addCxxKnownPath(b, ctx, exe, "libstdc++.a", null, need_cpp_includes); - exe.linkSystemLibrary("pthread"); - // TODO LLD cannot perform this link. - // Set ZIG_SYSTEM_LINKER_HACK env var to use system linker ld instead. - // See https://github.com/ziglang/zig/issues/1535 - } else |err| switch (err) { - error.RequiredLibraryNotFound => { - // System compiler, not gcc. - exe.linkSystemLibrary("c++"); - }, - else => |e| return e, - } - } - - if (ctx.dia_guids_lib.len != 0) { - exe.addObjectFile(ctx.dia_guids_lib); - } - } -} - fn addCxxKnownPath( b: *Builder, ctx: CMakeConfig, diff --git a/lib/std/array_list_sentineled.zig b/lib/std/array_list_sentineled.zig deleted file mode 100644 index 323b5eaaed..0000000000 --- a/lib/std/array_list_sentineled.zig +++ /dev/null @@ -1,229 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("std.zig"); -const debug = std.debug; -const mem = std.mem; -const Allocator = mem.Allocator; -const assert = debug.assert; -const testing = std.testing; -const ArrayList = std.ArrayList; - -/// A contiguous, growable list of items in memory, with a sentinel after them. -/// The sentinel is maintained when appending, resizing, etc. -/// If you do not need a sentinel, consider using `ArrayList` instead. -pub fn ArrayListSentineled(comptime T: type, comptime sentinel: T) type { - return struct { - list: ArrayList(T), - - const Self = @This(); - - /// Must deinitialize with deinit. - pub fn init(allocator: *Allocator, m: []const T) !Self { - var self = try initSize(allocator, m.len); - mem.copy(T, self.list.items, m); - return self; - } - - /// Initialize memory to size bytes of undefined values. - /// Must deinitialize with deinit. - pub fn initSize(allocator: *Allocator, size: usize) !Self { - var self = initNull(allocator); - try self.resize(size); - return self; - } - - /// Initialize with capacity to hold at least num bytes. - /// Must deinitialize with deinit. - pub fn initCapacity(allocator: *Allocator, num: usize) !Self { - var self = Self{ .list = try ArrayList(T).initCapacity(allocator, num + 1) }; - self.list.appendAssumeCapacity(sentinel); - return self; - } - - /// Must deinitialize with deinit. - /// None of the other operations are valid until you do one of these: - /// * `replaceContents` - /// * `resize` - pub fn initNull(allocator: *Allocator) Self { - return Self{ .list = ArrayList(T).init(allocator) }; - } - - /// Must deinitialize with deinit. - pub fn initFromBuffer(buffer: Self) !Self { - return Self.init(buffer.list.allocator, buffer.span()); - } - - /// Takes ownership of the passed in slice. The slice must have been - /// allocated with `allocator`. - /// Must deinitialize with deinit. - pub fn fromOwnedSlice(allocator: *Allocator, slice: []T) !Self { - var self = Self{ .list = ArrayList(T).fromOwnedSlice(allocator, slice) }; - try self.list.append(sentinel); - return self; - } - - /// The caller owns the returned memory. The list becomes null and is safe to `deinit`. - pub fn toOwnedSlice(self: *Self) [:sentinel]T { - const allocator = self.list.allocator; - const result = self.list.toOwnedSlice(); - self.* = initNull(allocator); - return result[0 .. result.len - 1 :sentinel]; - } - - /// Only works when `T` is `u8`. - pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: anytype) !Self { - const size = std.math.cast(usize, std.fmt.count(format, args)) catch |err| switch (err) { - error.Overflow => return error.OutOfMemory, - }; - var self = try Self.initSize(allocator, size); - assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size); - return self; - } - - pub fn deinit(self: *Self) void { - self.list.deinit(); - } - - pub fn span(self: anytype) @TypeOf(self.list.items[0..:sentinel]) { - return self.list.items[0..self.len() :sentinel]; - } - - pub fn shrink(self: *Self, new_len: usize) void { - assert(new_len <= self.len()); - self.list.shrink(new_len + 1); - self.list.items[self.len()] = sentinel; - } - - pub fn resize(self: *Self, new_len: usize) !void { - try self.list.resize(new_len + 1); - self.list.items[self.len()] = sentinel; - } - - pub fn isNull(self: Self) bool { - return self.list.items.len == 0; - } - - pub fn len(self: Self) usize { - return self.list.items.len - 1; - } - - pub fn capacity(self: Self) usize { - return if (self.list.capacity > 0) - self.list.capacity - 1 - else - 0; - } - - pub fn appendSlice(self: *Self, m: []const T) !void { - const old_len = self.len(); - try self.resize(old_len + m.len); - mem.copy(T, self.list.items[old_len..], m); - } - - pub fn append(self: *Self, byte: T) !void { - const old_len = self.len(); - try self.resize(old_len + 1); - self.list.items[old_len] = byte; - } - - pub fn eql(self: Self, m: []const T) bool { - return mem.eql(T, self.span(), m); - } - - pub fn startsWith(self: Self, m: []const T) bool { - if (self.len() < m.len) return false; - return mem.eql(T, self.list.items[0..m.len], m); - } - - pub fn endsWith(self: Self, m: []const T) bool { - const l = self.len(); - if (l < m.len) return false; - const start = l - m.len; - return mem.eql(T, self.list.items[start..l], m); - } - - pub fn replaceContents(self: *Self, m: []const T) !void { - try self.resize(m.len); - mem.copy(T, self.list.items, m); - } - - /// Initializes an OutStream which will append to the list. - /// This function may be called only when `T` is `u8`. - pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) { - return .{ .context = self }; - } - - /// Same as `append` except it returns the number of bytes written, which is always the same - /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API. - /// This function may be called only when `T` is `u8`. - pub fn appendWrite(self: *Self, m: []const u8) !usize { - try self.appendSlice(m); - return m.len; - } - }; -} - -test "simple" { - var buf = try ArrayListSentineled(u8, 0).init(testing.allocator, ""); - defer buf.deinit(); - - testing.expect(buf.len() == 0); - try buf.appendSlice("hello"); - try buf.appendSlice(" "); - try buf.appendSlice("world"); - testing.expect(buf.eql("hello world")); - testing.expect(mem.eql(u8, mem.spanZ(buf.span().ptr), buf.span())); - - var buf2 = try ArrayListSentineled(u8, 0).initFromBuffer(buf); - defer buf2.deinit(); - testing.expect(buf.eql(buf2.span())); - - testing.expect(buf.startsWith("hell")); - testing.expect(buf.endsWith("orld")); - - try buf2.resize(4); - testing.expect(buf.startsWith(buf2.span())); -} - -test "initSize" { - var buf = try ArrayListSentineled(u8, 0).initSize(testing.allocator, 3); - defer buf.deinit(); - testing.expect(buf.len() == 3); - try buf.appendSlice("hello"); - testing.expect(mem.eql(u8, buf.span()[3..], "hello")); -} - -test "initCapacity" { - var buf = try ArrayListSentineled(u8, 0).initCapacity(testing.allocator, 10); - defer buf.deinit(); - testing.expect(buf.len() == 0); - testing.expect(buf.capacity() >= 10); - const old_cap = buf.capacity(); - try buf.appendSlice("hello"); - testing.expect(buf.len() == 5); - testing.expect(buf.capacity() == old_cap); - testing.expect(mem.eql(u8, buf.span(), "hello")); -} - -test "print" { - var buf = try ArrayListSentineled(u8, 0).init(testing.allocator, ""); - defer buf.deinit(); - - try buf.outStream().print("Hello {d} the {s}", .{ 2, "world" }); - testing.expect(buf.eql("Hello 2 the world")); -} - -test "outStream" { - var buffer = try ArrayListSentineled(u8, 0).initSize(testing.allocator, 0); - defer buffer.deinit(); - const buf_stream = buffer.outStream(); - - const x: i32 = 42; - const y: i32 = 1234; - try buf_stream.print("x: {}\ny: {}\n", .{ x, y }); - - testing.expect(mem.eql(u8, buffer.span(), "x: 42\ny: 1234\n")); -} diff --git a/lib/std/progress.zig b/lib/std/progress.zig deleted file mode 100644 index 474ed050aa..0000000000 --- a/lib/std/progress.zig +++ /dev/null @@ -1,310 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. -const std = @import("std"); -const windows = std.os.windows; -const testing = std.testing; -const assert = std.debug.assert; - -/// This API is non-allocating and non-fallible. The tradeoff is that users of -/// this API must provide the storage for each `Progress.Node`. -/// Initialize the struct directly, overriding these fields as desired: -/// * `refresh_rate_ms` -/// * `initial_delay_ms` -pub const Progress = struct { - /// `null` if the current node (and its children) should - /// not print on update() - terminal: ?std.fs.File = undefined, - - /// Whether the terminal supports ANSI escape codes. - supports_ansi_escape_codes: bool = false, - - root: Node = undefined, - - /// Keeps track of how much time has passed since the beginning. - /// Used to compare with `initial_delay_ms` and `refresh_rate_ms`. - timer: std.time.Timer = undefined, - - /// When the previous refresh was written to the terminal. - /// Used to compare with `refresh_rate_ms`. - prev_refresh_timestamp: u64 = undefined, - - /// This buffer represents the maximum number of bytes written to the terminal - /// with each refresh. - output_buffer: [100]u8 = undefined, - - /// How many nanoseconds between writing updates to the terminal. - refresh_rate_ns: u64 = 50 * std.time.ns_per_ms, - - /// How many nanoseconds to keep the output hidden - initial_delay_ns: u64 = 500 * std.time.ns_per_ms, - - done: bool = true, - - /// Keeps track of how many columns in the terminal have been output, so that - /// we can move the cursor back later. - columns_written: usize = undefined, - - /// Represents one unit of progress. Each node can have children nodes, or - /// one can use integers with `update`. - pub const Node = struct { - context: *Progress, - parent: ?*Node, - completed_items: usize, - name: []const u8, - recently_updated_child: ?*Node = null, - - /// This field may be updated freely. - estimated_total_items: ?usize, - - /// Create a new child progress node. - /// Call `Node.end` when done. - /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this - /// API to set `self.parent.recently_updated_child` with the return value. - /// Until that is fixed you probably want to call `activate` on the return value. - pub fn start(self: *Node, name: []const u8, estimated_total_items: ?usize) Node { - return Node{ - .context = self.context, - .parent = self, - .completed_items = 0, - .name = name, - .estimated_total_items = estimated_total_items, - }; - } - - /// This is the same as calling `start` and then `end` on the returned `Node`. - pub fn completeOne(self: *Node) void { - if (self.parent) |parent| parent.recently_updated_child = self; - self.completed_items += 1; - self.context.maybeRefresh(); - } - - pub fn end(self: *Node) void { - self.context.maybeRefresh(); - if (self.parent) |parent| { - if (parent.recently_updated_child) |parent_child| { - if (parent_child == self) { - parent.recently_updated_child = null; - } - } - parent.completeOne(); - } else { - self.context.done = true; - self.context.refresh(); - } - } - - /// Tell the parent node that this node is actively being worked on. - pub fn activate(self: *Node) void { - if (self.parent) |parent| parent.recently_updated_child = self; - } - }; - - /// Create a new progress node. - /// Call `Node.end` when done. - /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this - /// API to return Progress rather than accept it as a parameter. - pub fn start(self: *Progress, name: []const u8, estimated_total_items: ?usize) !*Node { - const stderr = std.io.getStdErr(); - self.terminal = null; - if (stderr.supportsAnsiEscapeCodes()) { - self.terminal = stderr; - self.supports_ansi_escape_codes = true; - } else if (std.builtin.os.tag == .windows and stderr.isTty()) { - self.terminal = stderr; - } - self.root = Node{ - .context = self, - .parent = null, - .completed_items = 0, - .name = name, - .estimated_total_items = estimated_total_items, - }; - self.columns_written = 0; - self.prev_refresh_timestamp = 0; - self.timer = try std.time.Timer.start(); - self.done = false; - return &self.root; - } - - /// Updates the terminal if enough time has passed since last update. - pub fn maybeRefresh(self: *Progress) void { - const now = self.timer.read(); - if (now < self.initial_delay_ns) return; - if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return; - self.refresh(); - } - - /// Updates the terminal and resets `self.next_refresh_timestamp`. - pub fn refresh(self: *Progress) void { - const file = self.terminal orelse return; - - const prev_columns_written = self.columns_written; - var end: usize = 0; - if (self.columns_written > 0) { - // restore the cursor position by moving the cursor - // `columns_written` cells to the left, then clear the rest of the - // line - if (self.supports_ansi_escape_codes) { - end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len; - end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len; - } else if (std.builtin.os.tag == .windows) winapi: { - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) - unreachable; - - var cursor_pos = windows.COORD{ - .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written), - .Y = info.dwCursorPosition.Y, - }; - - if (cursor_pos.X < 0) - cursor_pos.X = 0; - - const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X); - - var written: windows.DWORD = undefined; - if (windows.kernel32.FillConsoleOutputAttribute( - file.handle, - info.wAttributes, - fill_chars, - cursor_pos, - &written, - ) != windows.TRUE) { - // Stop trying to write to this file. - self.terminal = null; - break :winapi; - } - if (windows.kernel32.FillConsoleOutputCharacterA( - file.handle, - ' ', - fill_chars, - cursor_pos, - &written, - ) != windows.TRUE) unreachable; - - if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) - unreachable; - } else unreachable; - - self.columns_written = 0; - } - - if (!self.done) { - var need_ellipse = false; - var maybe_node: ?*Node = &self.root; - while (maybe_node) |node| { - if (need_ellipse) { - self.bufWrite(&end, "... ", .{}); - } - need_ellipse = false; - if (node.name.len != 0 or node.estimated_total_items != null) { - if (node.name.len != 0) { - self.bufWrite(&end, "{s}", .{node.name}); - need_ellipse = true; - } - if (node.estimated_total_items) |total| { - if (need_ellipse) self.bufWrite(&end, " ", .{}); - self.bufWrite(&end, "[{d}/{d}] ", .{ node.completed_items + 1, total }); - need_ellipse = false; - } else if (node.completed_items != 0) { - if (need_ellipse) self.bufWrite(&end, " ", .{}); - self.bufWrite(&end, "[{d}] ", .{node.completed_items + 1}); - need_ellipse = false; - } - } - maybe_node = node.recently_updated_child; - } - if (need_ellipse) { - self.bufWrite(&end, "... ", .{}); - } - } - - _ = file.write(self.output_buffer[0..end]) catch |e| { - // Stop trying to write to this file once it errors. - self.terminal = null; - }; - self.prev_refresh_timestamp = self.timer.read(); - } - - pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void { - const file = self.terminal orelse return; - self.refresh(); - file.outStream().print(format, args) catch { - self.terminal = null; - return; - }; - self.columns_written = 0; - } - - fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void { - if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| { - const amt = written.len; - end.* += amt; - self.columns_written += amt; - } else |err| switch (err) { - error.NoSpaceLeft => { - self.columns_written += self.output_buffer.len - end.*; - end.* = self.output_buffer.len; - }, - } - const bytes_needed_for_esc_codes_at_end = if (std.builtin.os.tag == .windows) 0 else 11; - const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end; - if (end.* > max_end) { - const suffix = "... "; - self.columns_written = self.columns_written - (end.* - max_end) + suffix.len; - std.mem.copy(u8, self.output_buffer[max_end..], suffix); - end.* = max_end + suffix.len; - } - } -}; - -test "basic functionality" { - var disable = true; - if (disable) { - // This test is disabled because it uses time.sleep() and is therefore slow. It also - // prints bogus progress data to stderr. - return error.SkipZigTest; - } - var progress = Progress{}; - const root_node = try progress.start("", 100); - defer root_node.end(); - - const sub_task_names = [_][]const u8{ - "reticulating splines", - "adjusting shoes", - "climbing towers", - "pouring juice", - }; - var next_sub_task: usize = 0; - - var i: usize = 0; - while (i < 100) : (i += 1) { - var node = root_node.start(sub_task_names[next_sub_task], 5); - node.activate(); - next_sub_task = (next_sub_task + 1) % sub_task_names.len; - - node.completeOne(); - std.time.sleep(5 * std.time.ns_per_ms); - node.completeOne(); - node.completeOne(); - std.time.sleep(5 * std.time.ns_per_ms); - node.completeOne(); - node.completeOne(); - std.time.sleep(5 * std.time.ns_per_ms); - - node.end(); - - std.time.sleep(5 * std.time.ns_per_ms); - } - { - var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", null); - node.activate(); - std.time.sleep(10 * std.time.ns_per_ms); - progress.refresh(); - std.time.sleep(10 * std.time.ns_per_ms); - node.end(); - } -} diff --git a/src/codegen/c.zig b/src/codegen/c.zig index b97e1590e3..37654b8ce0 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -481,9 +481,8 @@ fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !? const rhs = try ctx.resolveInst(inst.rhs); const writer = file.main.writer(); const name = try ctx.name(); - try writer.writeAll(indentation ++ "const "); - try renderType(ctx, writer, inst.base.ty); - try writer.print(" {s} = {s} " ++ operator ++ " {s};\n", .{ name, lhs, rhs }); + try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); + try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs }); return name; } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig deleted file mode 100644 index 3a1ebada3b..0000000000 --- a/src/codegen/llvm.zig +++ /dev/null @@ -1,125 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; - -pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 { - const llvm_arch = switch (target.cpu.arch) { - .arm => "arm", - .armeb => "armeb", - .aarch64 => "aarch64", - .aarch64_be => "aarch64_be", - .aarch64_32 => "aarch64_32", - .arc => "arc", - .avr => "avr", - .bpfel => "bpfel", - .bpfeb => "bpfeb", - .hexagon => "hexagon", - .mips => "mips", - .mipsel => "mipsel", - .mips64 => "mips64", - .mips64el => "mips64el", - .msp430 => "msp430", - .powerpc => "powerpc", - .powerpc64 => "powerpc64", - .powerpc64le => "powerpc64le", - .r600 => "r600", - .amdgcn => "amdgcn", - .riscv32 => "riscv32", - .riscv64 => "riscv64", - .sparc => "sparc", - .sparcv9 => "sparcv9", - .sparcel => "sparcel", - .s390x => "s390x", - .tce => "tce", - .tcele => "tcele", - .thumb => "thumb", - .thumbeb => "thumbeb", - .i386 => "i386", - .x86_64 => "x86_64", - .xcore => "xcore", - .nvptx => "nvptx", - .nvptx64 => "nvptx64", - .le32 => "le32", - .le64 => "le64", - .amdil => "amdil", - .amdil64 => "amdil64", - .hsail => "hsail", - .hsail64 => "hsail64", - .spir => "spir", - .spir64 => "spir64", - .kalimba => "kalimba", - .shave => "shave", - .lanai => "lanai", - .wasm32 => "wasm32", - .wasm64 => "wasm64", - .renderscript32 => "renderscript32", - .renderscript64 => "renderscript64", - .ve => "ve", - .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, - }; - // TODO Add a sub-arch for some architectures depending on CPU features. - - const llvm_os = switch (target.os.tag) { - .freestanding => "unknown", - .ananas => "ananas", - .cloudabi => "cloudabi", - .dragonfly => "dragonfly", - .freebsd => "freebsd", - .fuchsia => "fuchsia", - .ios => "ios", - .kfreebsd => "kfreebsd", - .linux => "linux", - .lv2 => "lv2", - .macos => "macosx", - .netbsd => "netbsd", - .openbsd => "openbsd", - .solaris => "solaris", - .windows => "windows", - .haiku => "haiku", - .minix => "minix", - .rtems => "rtems", - .nacl => "nacl", - .cnk => "cnk", - .aix => "aix", - .cuda => "cuda", - .nvcl => "nvcl", - .amdhsa => "amdhsa", - .ps4 => "ps4", - .elfiamcu => "elfiamcu", - .tvos => "tvos", - .watchos => "watchos", - .mesa3d => "mesa3d", - .contiki => "contiki", - .amdpal => "amdpal", - .hermit => "hermit", - .hurd => "hurd", - .wasi => "wasi", - .emscripten => "emscripten", - .uefi => "windows", - .other => "unknown", - }; - - const llvm_abi = switch (target.abi) { - .none => "unknown", - .gnu => "gnu", - .gnuabin32 => "gnuabin32", - .gnuabi64 => "gnuabi64", - .gnueabi => "gnueabi", - .gnueabihf => "gnueabihf", - .gnux32 => "gnux32", - .code16 => "code16", - .eabi => "eabi", - .eabihf => "eabihf", - .android => "android", - .musl => "musl", - .musleabi => "musleabi", - .musleabihf => "musleabihf", - .msvc => "msvc", - .itanium => "itanium", - .cygnus => "cygnus", - .coreclr => "coreclr", - .simulator => "simulator", - .macabi => "macabi", - }; - - return std.fmt.allocPrint(allocator, "{s}-unknown-{s}-{s}", .{ llvm_arch, llvm_os, llvm_abi }); -} diff --git a/src/link/C.zig b/src/link/C.zig index 3ac9db717f..c3ab506b2c 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -111,9 +111,6 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { if (self.header.buf.items.len > 0) { try writer.writeByte('\n'); } - if (self.header.items.len > 0) { - try writer.print("{s}\n", .{self.header.items}); - } if (self.constants.items.len > 0) { try writer.print("{s}\n", .{self.constants.items}); } diff --git a/src/llvm_backend.zig b/src/llvm_backend.zig index 294e6f5400..51d1a0840e 100644 --- a/src/llvm_backend.zig +++ b/src/llvm_backend.zig @@ -132,7 +132,7 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { .macabi => "macabi", }; - return std.fmt.allocPrintZ(allocator, "{}-unknown-{}-{}", .{ llvm_arch, llvm_os, llvm_abi }); + return std.fmt.allocPrintZ(allocator, "{s}-unknown-{s}-{s}", .{ llvm_arch, llvm_os, llvm_abi }); } pub const LLVMIRModule = struct { -- cgit v1.2.3 From 974c008a0ee0e0d7933e37d5ea930f712d494f6a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 2 Jan 2021 19:03:14 -0700 Subject: convert more {} to {d} and {s} --- lib/std/SemanticVersion.zig | 2 +- lib/std/c/ast.zig | 8 ++++---- lib/std/fs/wasi.zig | 2 +- lib/std/os.zig | 4 ++-- lib/std/special/c.zig | 2 +- lib/std/special/compiler_rt.zig | 2 +- lib/std/testing.zig | 2 +- lib/std/zig/parser_test.zig | 4 ++-- lib/std/zig/system/macos.zig | 4 ++-- src/Compilation.zig | 12 ++++++------ src/DepTokenizer.zig | 6 +++--- src/Module.zig | 22 +++++++++++----------- src/astgen.zig | 6 +++--- src/codegen/c.zig | 6 +++--- src/glibc.zig | 28 ++++++++++++++-------------- src/link.zig | 2 +- src/link/Elf.zig | 6 +++--- src/liveness.zig | 3 ++- src/main.zig | 14 +++++++------- src/translate_c.zig | 18 +++++++++--------- src/type.zig | 8 ++++---- src/zir.zig | 19 +++++++++---------- src/zir_sema.zig | 14 +++++++------- 23 files changed, 97 insertions(+), 97 deletions(-) (limited to 'src/codegen') diff --git a/lib/std/SemanticVersion.zig b/lib/std/SemanticVersion.zig index eeb6916ad6..dc9e6d8572 100644 --- a/lib/std/SemanticVersion.zig +++ b/lib/std/SemanticVersion.zig @@ -163,7 +163,7 @@ pub fn format( out_stream: anytype, ) !void { if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'"); - try std.fmt.format(out_stream, "{}.{}.{}", .{ self.major, self.minor, self.patch }); + try std.fmt.format(out_stream, "{d}.{d}.{d}", .{ self.major, self.minor, self.patch }); if (self.pre) |pre| try std.fmt.format(out_stream, "-{s}", .{pre}); if (self.build) |build| try std.fmt.format(out_stream, "+{s}", .{build}); } diff --git a/lib/std/c/ast.zig b/lib/std/c/ast.zig index c1e20f799d..207fe8eac8 100644 --- a/lib/std/c/ast.zig +++ b/lib/std/c/ast.zig @@ -115,10 +115,10 @@ pub const Error = union(enum) { pub fn render(self: *const ExpectedToken, tree: *Tree, stream: anytype) !void { const found_token = tree.tokens.at(self.token); if (found_token.id == .Invalid) { - return stream.print("expected '{}', found invalid bytes", .{self.expected_id.symbol()}); + return stream.print("expected '{s}', found invalid bytes", .{self.expected_id.symbol()}); } else { const token_name = found_token.id.symbol(); - return stream.print("expected '{}', found '{}'", .{ self.expected_id.symbol(), token_name }); + return stream.print("expected '{s}', found '{s}'", .{ self.expected_id.symbol(), token_name }); } } }; @@ -131,7 +131,7 @@ pub const Error = union(enum) { try stream.write("invalid type specifier '"); try type_spec.spec.print(tree, stream); const token_name = tree.tokens.at(self.token).id.symbol(); - return stream.print("{}'", .{token_name}); + return stream.print("{s}'", .{token_name}); } }; @@ -140,7 +140,7 @@ pub const Error = union(enum) { name: TokenIndex, pub fn render(self: *const ExpectedToken, tree: *Tree, stream: anytype) !void { - return stream.print("must use '{}' tag to refer to type '{}'", .{ tree.slice(kw), tree.slice(name) }); + return stream.print("must use '{s}' tag to refer to type '{s}'", .{ tree.slice(kw), tree.slice(name) }); } }; diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index 761f6e8466..900adc5e2d 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -38,7 +38,7 @@ pub const PreopenType = union(PreopenTypeTag) { pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void { try out_stream.print("PreopenType{{ ", .{}); switch (self) { - PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{path}), + PreopenType.Dir => |path| try out_stream.print(".Dir = '{z}'", .{path}), } return out_stream.print(" }}", .{}); } diff --git a/lib/std/os.zig b/lib/std/os.zig index fb187252a1..1bde12a6fb 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4256,7 +4256,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { }, .linux => { var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined; - const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable; + const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{d}\x00", .{fd}) catch unreachable; const target = readlinkZ(std.meta.assumeSentinel(proc_path.ptr, 0), out_buffer) catch |err| { switch (err) { @@ -4487,7 +4487,7 @@ pub const UnexpectedError = error{ /// and you get an unexpected error. pub fn unexpectedErrno(err: usize) UnexpectedError { if (unexpected_error_tracing) { - std.debug.warn("unexpected errno: {}\n", .{err}); + std.debug.warn("unexpected errno: {d}\n", .{err}); std.debug.dumpCurrentStackTrace(null); } return error.Unexpected; diff --git a/lib/std/special/c.zig b/lib/std/special/c.zig index c92ffa21ac..51cbafc133 100644 --- a/lib/std/special/c.zig +++ b/lib/std/special/c.zig @@ -172,7 +172,7 @@ test "strncmp" { pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { if (builtin.is_test) { @setCold(true); - std.debug.panic("{}", .{msg}); + std.debug.panic("{s}", .{msg}); } if (builtin.os.tag != .freestanding and builtin.os.tag != .other) { std.os.abort(); diff --git a/lib/std/special/compiler_rt.zig b/lib/std/special/compiler_rt.zig index e0743cd4b7..a06c568b2e 100644 --- a/lib/std/special/compiler_rt.zig +++ b/lib/std/special/compiler_rt.zig @@ -324,7 +324,7 @@ pub usingnamespace @import("compiler_rt/atomics.zig"); pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { @setCold(true); if (is_test) { - std.debug.panic("{}", .{msg}); + std.debug.panic("{s}", .{msg}); } else { unreachable; } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 80cc7f930d..4f4a46dbf7 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -258,7 +258,7 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const // If the child type is u8 and no weird bytes, we could print it as strings // Even for the length difference, it would be useful to see the values of the slices probably. if (expected.len != actual.len) { - std.debug.panic("slice lengths differ. expected {}, found {}", .{ expected.len, actual.len }); + std.debug.panic("slice lengths differ. expected {d}, found {d}", .{ expected.len, actual.len }); } var i: usize = 0; while (i < expected.len) : (i += 1) { diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index a736006bc6..63d04f9a80 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -3742,7 +3742,7 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b for (tree.errors) |*parse_error| { const token = tree.token_locs[parse_error.loc()]; const loc = tree.tokenLocation(0, parse_error.loc()); - try stderr.print("(memory buffer):{}:{}: error: ", .{ loc.line + 1, loc.column + 1 }); + try stderr.print("(memory buffer):{d}:{d}: error: ", .{ loc.line + 1, loc.column + 1 }); try tree.renderError(parse_error, stderr); try stderr.print("\n{s}\n", .{source[loc.line_start..loc.line_end]}); { @@ -3800,7 +3800,7 @@ fn testTransform(source: []const u8, expected_source: []const u8) !void { error.OutOfMemory => { if (failing_allocator.allocated_bytes != failing_allocator.freed_bytes) { warn( - "\nfail_index: {}/{}\nallocated bytes: {}\nfreed bytes: {}\nallocations: {}\ndeallocations: {}\n", + "\nfail_index: {d}/{d}\nallocated bytes: {d}\nfreed bytes: {d}\nallocations: {d}\ndeallocations: {d}\n", .{ fail_index, needed_alloc_count, diff --git a/lib/std/zig/system/macos.zig b/lib/std/zig/system/macos.zig index fbc0b5149b..7c4e255cc5 100644 --- a/lib/std/zig/system/macos.zig +++ b/lib/std/zig/system/macos.zig @@ -450,7 +450,7 @@ test "version_from_build" { for (known) |pair| { var buf: [32]u8 = undefined; const ver = try version_from_build(pair[0]); - const sver = try std.fmt.bufPrint(buf[0..], "{}.{}.{}", .{ ver.major, ver.minor, ver.patch }); + const sver = try std.fmt.bufPrint(buf[0..], "{d}.{d}.{d}", .{ ver.major, ver.minor, ver.patch }); std.testing.expect(std.mem.eql(u8, sver, pair[1])); } } @@ -468,7 +468,7 @@ pub fn getSDKPath(allocator: *mem.Allocator) ![]u8 { allocator.free(result.stdout); } if (result.stderr.len != 0) { - std.log.err("unexpected 'xcrun --show-sdk-path' stderr: {}", .{result.stderr}); + std.log.err("unexpected 'xcrun --show-sdk-path' stderr: {s}", .{result.stderr}); } if (result.term.Exited != 0) { return error.ProcessTerminated; diff --git a/src/Compilation.zig b/src/Compilation.zig index c688cf7980..de115b9b40 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1512,7 +1512,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( module.gpa, decl.src(), - "unable to generate C header: {}", + "unable to generate C header: {s}", .{@errorName(err)}, )); decl.analysis = .codegen_failure_retryable; @@ -1593,7 +1593,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .libtsan => { libtsan.buildTsan(self) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build TSAN library: {}", .{@errorName(err)}); + fatal("unable to build TSAN library: {s}", .{@errorName(err)}); }; }, .compiler_rt_lib => { @@ -1983,7 +1983,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * // TODO parse clang stderr and turn it into an error message // and then call failCObjWithOwnedErrorMsg log.err("clang failed with stderr: {s}", .{stderr}); - return comp.failCObj(c_object, "clang exited with code {}", .{code}); + return comp.failCObj(c_object, "clang exited with code {d}", .{code}); } }, else => { @@ -3013,7 +3013,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("stage1 {} new_digest={} error: {s}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); + log.debug("stage1 {s} new_digest={} error: {s}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -3021,7 +3021,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node if (!mem.eql(u8, prev_digest[0..digest.len], &digest)) break :hit; - log.debug("stage1 {} digest={} match - skipping invocation", .{ mod.root_pkg.root_src_path, digest }); + log.debug("stage1 {s} digest={} match - skipping invocation", .{ mod.root_pkg.root_src_path, digest }); var flags_bytes: [1]u8 = undefined; _ = std.fmt.hexToBytes(&flags_bytes, prev_digest[digest.len..]) catch { log.warn("bad cache stage1 digest: '{s}'", .{prev_digest}); @@ -3044,7 +3044,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node mod.stage1_flags = @bitCast(@TypeOf(mod.stage1_flags), flags_bytes[0]); return; } - log.debug("stage1 {} prev_digest={} new_digest={}", .{ mod.root_pkg.root_src_path, prev_digest, digest }); + log.debug("stage1 {s} prev_digest={} new_digest={}", .{ mod.root_pkg.root_src_path, prev_digest, digest }); man.unhit(prev_hash_state, input_file_count); } diff --git a/src/DepTokenizer.zig b/src/DepTokenizer.zig index c42583a786..0872513b51 100644 --- a/src/DepTokenizer.zig +++ b/src/DepTokenizer.zig @@ -373,7 +373,7 @@ pub const Token = union(enum) { } else { try printCharValues(writer, index_and_bytes.bytes); } - try writer.print("' at position {}", .{index_and_bytes.index}); + try writer.print("' at position {d}", .{index_and_bytes.index}); }, .invalid_target, .bad_target_escape, @@ -383,7 +383,7 @@ pub const Token = union(enum) { => |index_and_char| { try writer.writeAll("illegal char "); try printUnderstandableChar(writer, index_and_char.char); - try writer.print(" at position {}: {s}", .{ index_and_char.index, self.errStr() }); + try writer.print(" at position {d}: {s}", .{ index_and_char.index, self.errStr() }); }, } } @@ -943,7 +943,7 @@ fn printSection(out: anytype, label: []const u8, bytes: []const u8) !void { fn printLabel(out: anytype, label: []const u8, bytes: []const u8) !void { var buf: [80]u8 = undefined; - var text = try std.fmt.bufPrint(buf[0..], "{s} {} bytes ", .{ label, bytes.len }); + var text = try std.fmt.bufPrint(buf[0..], "{s} {d} bytes ", .{ label, bytes.len }); try out.writeAll(text); var i: usize = text.len; const end = 79; diff --git a/src/Module.zig b/src/Module.zig index 0d03703dcb..d1719059c4 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -248,7 +248,7 @@ pub const Decl = struct { pub fn dump(self: *Decl) void { const loc = std.zig.findLineColumn(self.scope.source.bytes, self.src); - std.debug.print("{}:{}:{} name={} status={}", .{ + std.debug.print("{s}:{d}:{d} name={s} status={s}", .{ self.scope.sub_file_path, loc.line + 1, loc.column + 1, @@ -308,7 +308,7 @@ pub const Fn = struct { /// For debugging purposes. pub fn dump(self: *Fn, mod: Module) void { - std.debug.print("Module.Function(name={}) ", .{self.owner_decl.name}); + std.debug.print("Module.Function(name={s}) ", .{self.owner_decl.name}); switch (self.analysis) { .queued => { std.debug.print("queued\n", .{}); @@ -632,7 +632,7 @@ pub const Scope = struct { pub fn dumpSrc(self: *File, src: usize) void { const loc = std.zig.findLineColumn(self.source.bytes, src); - std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); + std.debug.print("{s}:{d}:{d}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); } pub fn getSource(self: *File, module: *Module) ![:0]const u8 { @@ -730,7 +730,7 @@ pub const Scope = struct { pub fn dumpSrc(self: *ZIRModule, src: usize) void { const loc = std.zig.findLineColumn(self.source.bytes, src); - std.debug.print("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); + std.debug.print("{s}:{d}:{d}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); } pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 { @@ -1641,7 +1641,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void } } else if (src_decl.castTag(.Comptime)) |comptime_node| { const name_index = self.getNextAnonNameIndex(); - const name = try std.fmt.allocPrint(self.gpa, "__comptime_{}", .{name_index}); + const name = try std.fmt.allocPrint(self.gpa, "__comptime_{d}", .{name_index}); defer self.gpa.free(name); const name_hash = container_scope.fullyQualifiedNameHash(name); @@ -2277,7 +2277,7 @@ pub fn createAnonymousDecl( ) !*Decl { const name_index = self.getNextAnonNameIndex(); const scope_decl = scope.decl().?; - const name = try std.fmt.allocPrint(self.gpa, "{s}__anon_{}", .{ scope_decl.name, name_index }); + const name = try std.fmt.allocPrint(self.gpa, "{s}__anon_{d}", .{ scope_decl.name, name_index }); defer self.gpa.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); const src_hash: std.zig.SrcHash = undefined; @@ -2555,7 +2555,7 @@ pub fn cmpNumeric( if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) { if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) { - return self.fail(scope, src, "vector length mismatch: {} and {}", .{ + return self.fail(scope, src, "vector length mismatch: {d} and {d}", .{ lhs.ty.arrayLen(), rhs.ty.arrayLen(), }); @@ -2700,7 +2700,7 @@ pub fn cmpNumeric( const dest_type = if (dest_float_type) |ft| ft else blk: { const max_bits = std.math.max(lhs_bits, rhs_bits); const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) { - error.Overflow => return self.fail(scope, src, "{} exceeds maximum integer bit count", .{max_bits}), + error.Overflow => return self.fail(scope, src, "{d} exceeds maximum integer bit count", .{max_bits}), }; break :blk try self.makeIntType(scope, dest_int_is_signed, casted_bits); }; @@ -3319,7 +3319,7 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void { const source = zir_module.getSource(self) catch @panic("dumpInst failed to get source"); const loc = std.zig.findLineColumn(source, inst.src); if (inst.tag == .constant) { - std.debug.print("constant ty={} val={} src={}:{}:{}\n", .{ + std.debug.print("constant ty={} val={} src={s}:{d}:{d}\n", .{ inst.ty, inst.castTag(.constant).?.val, zir_module.subFilePath(), @@ -3327,7 +3327,7 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void { loc.column + 1, }); } else if (inst.deaths == 0) { - std.debug.print("{} ty={} src={}:{}:{}\n", .{ + std.debug.print("{s} ty={} src={s}:{d}:{d}\n", .{ @tagName(inst.tag), inst.ty, zir_module.subFilePath(), @@ -3335,7 +3335,7 @@ pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void { loc.column + 1, }); } else { - std.debug.print("{} ty={} deaths={b} src={}:{}:{}\n", .{ + std.debug.print("{s} ty={} deaths={b} src={s}:{d}:{d}\n", .{ @tagName(inst.tag), inst.ty, inst.deaths, diff --git a/src/astgen.zig b/src/astgen.zig index b3cb648dcb..a50fa026ca 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -385,7 +385,7 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpr .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, else => if (node.getLabel()) |break_label| { const label_name = try identifierTokenString(mod, parent_scope, break_label); - return mod.failTok(parent_scope, break_label, "label not found: '{}'", .{label_name}); + return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name}); } else { return mod.failTok(parent_scope, src, "break expression outside loop", .{}); }, @@ -427,7 +427,7 @@ fn continueExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowE .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, else => if (node.getLabel()) |break_label| { const label_name = try identifierTokenString(mod, parent_scope, break_label); - return mod.failTok(parent_scope, break_label, "label not found: '{}'", .{label_name}); + return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name}); } else { return mod.failTok(parent_scope, src, "continue expression outside loop", .{}); }, @@ -2204,7 +2204,7 @@ fn ensureBuiltinParamCount(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinC return; const s = if (count == 1) "" else "s"; - return mod.failTok(scope, call.builtin_token, "expected {} parameter{s}, found {}", .{ count, s, call.params_len }); + return mod.failTok(scope, call.builtin_token, "expected {d} parameter{s}, found {d}", .{ count, s, call.params_len }); } fn simpleCast( diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 37654b8ce0..684a03eb79 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -383,7 +383,7 @@ const Context = struct { } fn name(self: *Context) ![]u8 { - const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{}", .{self.unnamed_index}); + const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{d}", .{self.unnamed_index}); self.unnamed_index += 1; return val; } @@ -420,7 +420,7 @@ fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 { } fn genArg(ctx: *Context) !?[]u8 { - const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{}", .{ctx.argdex}); + const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{d}", .{ctx.argdex}); ctx.argdex += 1; return name; } @@ -528,7 +528,7 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { try renderValue(ctx, writer, arg.ty, val); } else { const val = try ctx.resolveInst(arg); - try writer.print("{}", .{val}); + try writer.print("{s}", .{val}); } } } diff --git a/src/glibc.zig b/src/glibc.zig index f69dd11ada..e7b7b1b1cf 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -111,12 +111,12 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! while (it.next()) |line| : (line_i += 1) { const prefix = "GLIBC_"; if (!mem.startsWith(u8, line, prefix)) { - std.log.err("vers.txt:{}: expected 'GLIBC_' prefix", .{line_i}); + std.log.err("vers.txt:{d}: expected 'GLIBC_' prefix", .{line_i}); return error.ZigInstallationCorrupt; } const adjusted_line = line[prefix.len..]; const ver = std.builtin.Version.parse(adjusted_line) catch |err| { - std.log.err("vers.txt:{}: unable to parse glibc version '{s}': {s}", .{ line_i, line, @errorName(err) }); + std.log.err("vers.txt:{d}: unable to parse glibc version '{s}': {s}", .{ line_i, line, @errorName(err) }); return error.ZigInstallationCorrupt; }; try all_versions.append(arena, ver); @@ -128,15 +128,15 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! while (file_it.next()) |line| : (line_i += 1) { var line_it = mem.tokenize(line, " "); const fn_name = line_it.next() orelse { - std.log.err("fns.txt:{}: expected function name", .{line_i}); + std.log.err("fns.txt:{d}: expected function name", .{line_i}); return error.ZigInstallationCorrupt; }; const lib_name = line_it.next() orelse { - std.log.err("fns.txt:{}: expected library name", .{line_i}); + std.log.err("fns.txt:{d}: expected library name", .{line_i}); return error.ZigInstallationCorrupt; }; const lib = findLib(lib_name) orelse { - std.log.err("fns.txt:{}: unknown library name: {s}", .{ line_i, lib_name }); + std.log.err("fns.txt:{d}: unknown library name: {s}", .{ line_i, lib_name }); return error.ZigInstallationCorrupt; }; try all_functions.append(arena, .{ @@ -158,27 +158,27 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! while (line_it.next()) |target_string| { var component_it = mem.tokenize(target_string, "-"); const arch_name = component_it.next() orelse { - std.log.err("abi.txt:{}: expected arch name", .{line_i}); + std.log.err("abi.txt:{d}: expected arch name", .{line_i}); return error.ZigInstallationCorrupt; }; const os_name = component_it.next() orelse { - std.log.err("abi.txt:{}: expected OS name", .{line_i}); + std.log.err("abi.txt:{d}: expected OS name", .{line_i}); return error.ZigInstallationCorrupt; }; const abi_name = component_it.next() orelse { - std.log.err("abi.txt:{}: expected ABI name", .{line_i}); + std.log.err("abi.txt:{d}: expected ABI name", .{line_i}); return error.ZigInstallationCorrupt; }; const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse { - std.log.err("abi.txt:{}: unrecognized arch: '{s}'", .{ line_i, arch_name }); + std.log.err("abi.txt:{d}: unrecognized arch: '{s}'", .{ line_i, arch_name }); return error.ZigInstallationCorrupt; }; if (!mem.eql(u8, os_name, "linux")) { - std.log.err("abi.txt:{}: expected OS 'linux', found '{s}'", .{ line_i, os_name }); + std.log.err("abi.txt:{d}: expected OS 'linux', found '{s}'", .{ line_i, os_name }); return error.ZigInstallationCorrupt; } const abi_tag = std.meta.stringToEnum(std.Target.Abi, abi_name) orelse { - std.log.err("abi.txt:{}: unrecognized ABI: '{s}'", .{ line_i, abi_name }); + std.log.err("abi.txt:{d}: unrecognized ABI: '{s}'", .{ line_i, abi_name }); return error.ZigInstallationCorrupt; }; @@ -193,7 +193,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! }; for (ver_list_base) |*ver_list| { const line = file_it.next() orelse { - std.log.err("abi.txt:{}: missing version number line", .{line_i}); + std.log.err("abi.txt:{d}: missing version number line", .{line_i}); return error.ZigInstallationCorrupt; }; line_i += 1; @@ -206,12 +206,12 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! while (line_it.next()) |version_index_string| { if (ver_list.len >= ver_list.versions.len) { // If this happens with legit data, increase the array len in the type. - std.log.err("abi.txt:{}: too many versions", .{line_i}); + std.log.err("abi.txt:{d}: too many versions", .{line_i}); return error.ZigInstallationCorrupt; } const version_index = std.fmt.parseInt(u8, version_index_string, 10) catch |err| { // If this happens with legit data, increase the size of the integer type in the struct. - std.log.err("abi.txt:{}: unable to parse version: {s}", .{ line_i, @errorName(err) }); + std.log.err("abi.txt:{d}: unable to parse version: {s}", .{ line_i, @errorName(err) }); return error.ZigInstallationCorrupt; }; diff --git a/src/link.zig b/src/link.zig index 92702c7973..3dbfb3b922 100644 --- a/src/link.zig +++ b/src/link.zig @@ -523,7 +523,7 @@ pub const File = struct { id_symlink_basename, &prev_digest_buf, ) catch |err| b: { - log.debug("archive new_digest={} readFile error: {}", .{ digest, @errorName(err) }); + log.debug("archive new_digest={} readFile error: {s}", .{ digest, @errorName(err) }); break :b prev_digest_buf[0..0]; }; if (mem.eql(u8, prev_digest, &digest)) { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 5d0e897008..116d7c9859 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2082,10 +2082,10 @@ pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); if (self.local_symbol_free_list.popOrNull()) |i| { - log.debug("reusing symbol index {} for {s}\n", .{ i, decl.name }); + log.debug("reusing symbol index {d} for {s}\n", .{ i, decl.name }); decl.link.elf.local_sym_index = i; } else { - log.debug("allocating symbol index {} for {s}\n", .{ self.local_symbols.items.len, decl.name }); + log.debug("allocating symbol index {d} for {s}\n", .{ self.local_symbols.items.len, decl.name }); decl.link.elf.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -2432,7 +2432,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { if (needed_size > self.allocatedSize(debug_line_sect.sh_offset)) { const new_offset = self.findFreeSpace(needed_size, 1); const existing_size = last_src_fn.off; - log.debug("moving .debug_line section: {} bytes from 0x{x} to 0x{x}\n", .{ + log.debug("moving .debug_line section: {d} bytes from 0x{x} to 0x{x}\n", .{ existing_size, debug_line_sect.sh_offset, new_offset, diff --git a/src/liveness.zig b/src/liveness.zig index 0d759f8312..b0aafa28f1 100644 --- a/src/liveness.zig +++ b/src/liveness.zig @@ -1,6 +1,7 @@ const std = @import("std"); const ir = @import("ir.zig"); const trace = @import("tracy.zig").trace; +const log = std.log.scoped(.liveness); /// Perform Liveness Analysis over the `Body`. Each `Inst` will have its `deaths` field populated. pub fn analyze( @@ -248,5 +249,5 @@ fn analyzeInst( @panic("Handle liveness analysis for instructions with many parameters"); } - std.log.scoped(.liveness).debug("analyze {}: 0b{b}\n", .{ base.tag, base.deaths }); + log.debug("analyze {}: 0b{b}\n", .{ base.tag, base.deaths }); } diff --git a/src/main.zig b/src/main.zig index 7d03ea956f..b1243badff 100644 --- a/src/main.zig +++ b/src/main.zig @@ -626,7 +626,7 @@ fn buildOutputType( fs.path.dirname(pkg_path), fs.path.basename(pkg_path), ) catch |err| { - fatal("Failed to add package at path {}: {}", .{ pkg_path, @errorName(err) }); + fatal("Failed to add package at path {s}: {s}", .{ pkg_path, @errorName(err) }); }; new_cur_pkg.parent = cur_pkg; try cur_pkg.add(gpa, pkg_name, new_cur_pkg); @@ -696,13 +696,13 @@ fn buildOutputType( if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; stack_size_override = std.fmt.parseUnsigned(u64, args[i], 0) catch |err| { - fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "--image-base")) { if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); i += 1; image_base_override = std.fmt.parseUnsigned(u64, args[i], 0) catch |err| { - fatal("unable to parse '{}': {}", .{ arg, @errorName(err) }); + fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; } else if (mem.eql(u8, arg, "--name")) { if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg}); @@ -1914,7 +1914,7 @@ fn buildOutputType( if (!watch) return cleanExit(); } else { const cmd = try argvCmd(arena, argv.items); - fatal("the following test command failed with exit code {}:\n{s}", .{ code, cmd }); + fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd }); } }, else => { @@ -2069,7 +2069,7 @@ fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !voi error.ASTUnitFailure => fatal("clang API returned errors but due to a clang bug, it is not exposing the errors for zig to see. For more details: https://github.com/ziglang/zig/issues/4455", .{}), error.SemanticAnalyzeFail => { for (clang_errors) |clang_err| { - std.debug.print("{s}:{}:{}: {s}\n", .{ + std.debug.print("{s}:{d}:{d}: {s}\n", .{ if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)", clang_err.line + 1, clang_err.column + 1, @@ -2493,7 +2493,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v .Exited => |code| { if (code == 0) return cleanExit(); const cmd = try argvCmd(arena, child_argv); - fatal("the following build command failed with exit code {}:\n{s}", .{ code, cmd }); + fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); }, else => { const cmd = try argvCmd(arena, child_argv); @@ -2812,7 +2812,7 @@ fn printErrMsgToFile( const text = text_buf.items; const stream = file.outStream(); - try stream.print("{s}:{}:{}: error: {s}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text }); + try stream.print("{s}:{d}:{d}: error: {s}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text }); if (!color_on) return; diff --git a/src/translate_c.zig b/src/translate_c.zig index c609597770..1b6455fd4d 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -136,7 +136,7 @@ const Scope = struct { var proposed_name = name_copy; while (scope.contains(proposed_name)) { scope.mangle_count += 1; - proposed_name = try std.fmt.allocPrint(c.arena, "{s}_{}", .{ name, scope.mangle_count }); + proposed_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ name, scope.mangle_count }); } try scope.variables.append(.{ .name = name_copy, .alias = proposed_name }); return proposed_name; @@ -440,7 +440,7 @@ pub fn translate( mem.copy(*ast.Node, root_node.decls(), context.root_decls.items); if (false) { - std.debug.warn("debug source:\n{}\n==EOF==\ntokens:\n", .{source_buffer.items}); + std.debug.warn("debug source:\n{s}\n==EOF==\ntokens:\n", .{source_buffer.items}); for (context.token_ids.items) |token| { std.debug.warn("{}\n", .{token}); } @@ -945,7 +945,7 @@ fn transRecordDecl(c: *Context, record_decl: *const clang.RecordDecl) Error!?*as // Record declarations such as `struct {...} x` have no name but they're not // anonymous hence here isAnonymousStructOrUnion is not needed if (bare_name.len == 0) { - bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{}", .{c.getMangle()}); + bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()}); is_unnamed = true; } @@ -1019,7 +1019,7 @@ fn transRecordDecl(c: *Context, record_decl: *const clang.RecordDecl) Error!?*as var raw_name = try c.str(@ptrCast(*const clang.NamedDecl, field_decl).getName_bytes_begin()); if (field_decl.isAnonymousStructOrUnion() or raw_name.len == 0) { // Context.getMangle() is not used here because doing so causes unpredictable field names for anonymous fields. - raw_name = try std.fmt.allocPrint(c.arena, "unnamed_{}", .{unnamed_field_count}); + raw_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{unnamed_field_count}); unnamed_field_count += 1; is_anon = true; } @@ -1110,7 +1110,7 @@ fn transEnumDecl(c: *Context, enum_decl: *const clang.EnumDecl) Error!?*ast.Node var bare_name = try c.str(@ptrCast(*const clang.NamedDecl, enum_decl).getName_bytes_begin()); var is_unnamed = false; if (bare_name.len == 0) { - bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{}", .{c.getMangle()}); + bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()}); is_unnamed = true; } @@ -3956,7 +3956,7 @@ fn qualTypeToLog2IntRef(rp: RestorePoint, qt: clang.QualType, source_loc: clang. const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .IntegerLiteral }, - .token = try appendTokenFmt(rp.c, .Identifier, "u{}", .{cast_bit_width}), + .token = try appendTokenFmt(rp.c, .Identifier, "u{d}", .{cast_bit_width}), }; return &node.base; } @@ -4484,7 +4484,7 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a _ = try appendToken(c, .Comma, ","); } const param_name_tok = param.name_token orelse - try appendTokenFmt(c, .Identifier, "arg_{}", .{c.getMangle()}); + try appendTokenFmt(c, .Identifier, "arg_{d}", .{c.getMangle()}); _ = try appendToken(c, .Colon, ":"); @@ -5916,11 +5916,11 @@ fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!* // struct Foo will be declared as struct_Foo by transRecordDecl const next_id = m.next().?; if (next_id != .Identifier) { - try m.fail(c, "unable to translate C expr: expected Identifier instead got: {}", .{@tagName(next_id)}); + try m.fail(c, "unable to translate C expr: expected Identifier instead got: {s}", .{@tagName(next_id)}); return error.ParseError; } - const ident_token = try appendTokenFmt(c, .Identifier, "{}_{}", .{ slice, m.slice() }); + const ident_token = try appendTokenFmt(c, .Identifier, "{s}_{s}", .{ slice, m.slice() }); const identifier = try c.arena.create(ast.Node.OneToken); identifier.* = .{ .base = .{ .tag = .Identifier }, diff --git a/src/type.zig b/src/type.zig index bb2e3b3bc2..9e2cd321f0 100644 --- a/src/type.zig +++ b/src/type.zig @@ -558,21 +558,21 @@ pub const Type = extern union { }, .array_u8 => { const len = ty.castTag(.array_u8).?.data; - return out_stream.print("[{}]u8", .{len}); + return out_stream.print("[{d}]u8", .{len}); }, .array_u8_sentinel_0 => { const len = ty.castTag(.array_u8_sentinel_0).?.data; - return out_stream.print("[{}:0]u8", .{len}); + return out_stream.print("[{d}:0]u8", .{len}); }, .array => { const payload = ty.castTag(.array).?.data; - try out_stream.print("[{}]", .{payload.len}); + try out_stream.print("[{d}]", .{payload.len}); ty = payload.elem_type; continue; }, .array_sentinel => { const payload = ty.castTag(.array_sentinel).?.data; - try out_stream.print("[{}:{}]", .{ payload.len, payload.sentinel }); + try out_stream.print("[{d}:{}]", .{ payload.len, payload.sentinel }); ty = payload.elem_type; continue; }, diff --git a/src/zir.zig b/src/zir.zig index 94e2b24b0c..0593cbd8fd 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1257,12 +1257,12 @@ const Writer = struct { self.next_instr_index += 1; try self.inst_table.putNoClobber(inst, .{ .inst = inst, .index = my_i, .name = undefined }); try stream.writeByteNTimes(' ', self.indent); - try stream.print("%{} ", .{my_i}); + try stream.print("%{d} ", .{my_i}); if (inst.cast(Inst.Block)) |block| { - const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{}", .{my_i}); + const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{d}", .{my_i}); try self.block_table.put(block, name); } else if (inst.cast(Inst.Loop)) |loop| { - const name = try std.fmt.allocPrint(&self.arena.allocator, "loop_{}", .{my_i}); + const name = try std.fmt.allocPrint(&self.arena.allocator, "loop_{d}", .{my_i}); try self.loop_table.put(loop, name); } self.indent += 2; @@ -1332,7 +1332,7 @@ const Writer = struct { fn writeInstParamToStream(self: *Writer, stream: anytype, inst: *Inst) !void { if (self.inst_table.get(inst)) |info| { if (info.index) |i| { - try stream.print("%{}", .{info.index}); + try stream.print("%{d}", .{info.index}); } else { try stream.print("@{s}", .{info.name}); } @@ -1660,7 +1660,6 @@ const Parser = struct { .contents_hash = std.zig.hashSrc(self.source[contents_start..self.i]), .inst = &inst_specific.base, }; - //std.debug.warn("parsed {} = '{}'\n", .{ inst_specific.base.name, inst_specific.base.contents }); return decl; } @@ -1805,7 +1804,7 @@ const Parser = struct { } fn generateName(self: *Parser) ![]u8 { - const result = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${}", .{self.unnamed_index}); + const result = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${d}", .{self.unnamed_index}); self.unnamed_index += 1; return result; } @@ -2865,7 +2864,7 @@ const EmitZIR = struct { fn autoName(self: *EmitZIR) ![]u8 { while (true) { - const proposed_name = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${}", .{self.next_auto_name}); + const proposed_name = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${d}", .{self.next_auto_name}); self.next_auto_name += 1; const gop = try self.names.getOrPut(proposed_name); if (!gop.found_existing) { @@ -2954,15 +2953,15 @@ pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8 write.next_instr_index += 1; if (inst.cast(Inst.Block)) |block| { - const name = try std.fmt.allocPrint(&write.arena.allocator, "label_{}", .{my_i}); + const name = try std.fmt.allocPrint(&write.arena.allocator, "label_{d}", .{my_i}); try write.block_table.put(block, name); } else if (inst.cast(Inst.Loop)) |loop| { - const name = try std.fmt.allocPrint(&write.arena.allocator, "loop_{}", .{my_i}); + const name = try std.fmt.allocPrint(&write.arena.allocator, "loop_{d}", .{my_i}); try write.loop_table.put(loop, name); } try write.inst_table.putNoClobber(inst, .{ .inst = inst, .index = my_i, .name = "inst" }); - try stderr.print(" %{} ", .{my_i}); + try stderr.print(" %{d} ", .{my_i}); try write.writeInstToStream(stderr, inst); try stderr.writeByte('\n'); } diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 72a1b04238..f9cd0e1a3d 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -535,7 +535,7 @@ fn analyzeInstParamType(mod: *Module, scope: *Scope, inst: *zir.Inst.ParamType) // TODO support C-style var args const param_count = fn_ty.fnParamLen(); if (arg_index >= param_count) { - return mod.fail(scope, inst.base.src, "arg index {} out of bounds; '{}' has {} argument(s)", .{ + return mod.fail(scope, inst.base.src, "arg index {d} out of bounds; '{}' has {d} argument(s)", .{ arg_index, fn_ty, param_count, @@ -580,7 +580,7 @@ fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!* const param_index = b.instructions.items.len; const param_count = fn_ty.fnParamLen(); if (param_index >= param_count) { - return mod.fail(scope, inst.base.src, "parameter index {} outside list of length {}", .{ + return mod.fail(scope, inst.base.src, "parameter index {d} outside list of length {d}", .{ param_index, param_count, }); @@ -790,7 +790,7 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError return mod.fail( scope, inst.positionals.func.src, - "expected at least {} argument(s), found {}", + "expected at least {d} argument(s), found {d}", .{ fn_params_len, call_params_len }, ); } @@ -800,7 +800,7 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError return mod.fail( scope, inst.positionals.func.src, - "expected {} argument(s), found {}", + "expected {d} argument(s), found {d}", .{ fn_params_len, call_params_len }, ); } @@ -1545,7 +1545,7 @@ fn analyzeInstBitwise(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerE if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) { if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) { - return mod.fail(scope, inst.base.src, "vector length mismatch: {} and {}", .{ + return mod.fail(scope, inst.base.src, "vector length mismatch: {d} and {d}", .{ lhs.ty.arrayLen(), rhs.ty.arrayLen(), }); @@ -1620,7 +1620,7 @@ fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inn if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) { if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) { - return mod.fail(scope, inst.base.src, "vector length mismatch: {} and {}", .{ + return mod.fail(scope, inst.base.src, "vector length mismatch: {d} and {d}", .{ lhs.ty.arrayLen(), rhs.ty.arrayLen(), }); @@ -1791,7 +1791,7 @@ fn analyzeInstCmp( return mod.cmpNumeric(scope, inst.base.src, lhs, rhs, op); } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) { if (!is_equality_cmp) { - return mod.fail(scope, inst.base.src, "{} operator not allowed for types", .{@tagName(op)}); + return mod.fail(scope, inst.base.src, "{s} operator not allowed for types", .{@tagName(op)}); } return mod.constBool(scope, inst.base.src, lhs.value().?.eql(rhs.value().?) == (op == .eq)); } -- cgit v1.2.3 From 9362f382ab7023592cc1d71044217b847b122406 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 2 Jan 2021 12:32:30 -0700 Subject: stage2: implement function call inlining in the frontend * remove the -Ddump-zir thing. that's handled through --verbose-ir * rework Fn to have an is_inline flag without requiring any more memory on the heap per function. * implement a rough first version of dumping typed zir (tzir) which is a lot more helpful for debugging than what we had before. We don't have a way to parse it though. * keep track of whether the inline-ness of a function changes because if it does we have to go update callsites. * add compile error for inline and export used together. inline function calls and comptime function calls are implemented the same way. A block instruction is set up to capture the result, and then a scope is set up that has a flag for is_comptime and some state if the scope is being inlined. when analyzing `ret` instructions, zig looks for inlining state in the scope, and if found, treats `ret` as a `break` instruction instead, with the target block being the one set up at the inline callsite. Follow-up items: * Complete out the debug TZIR dumping code. * Don't redundantly generate ZIR for each inline/comptime function call. Instead we should add a new state enum tag to Fn. * comptime and inlining branch quotas. * Add more test cases. --- build.zig | 2 - src/Compilation.zig | 14 +- src/Module.zig | 150 ++++++++++++----- src/codegen.zig | 10 +- src/codegen/c.zig | 2 +- src/codegen/wasm.zig | 2 +- src/config.zig.in | 1 - src/link/Elf.zig | 10 -- src/link/MachO/DebugSymbols.zig | 10 -- src/llvm_backend.zig | 2 +- src/zir.zig | 349 ++++++++++++++++++++++++++++------------ src/zir_sema.zig | 239 ++++++++++++++++++--------- test/stage2/zir.zig | 12 +- 13 files changed, 549 insertions(+), 254 deletions(-) (limited to 'src/codegen') diff --git a/build.zig b/build.zig index 2b0685d19e..f86b0d3bec 100644 --- a/build.zig +++ b/build.zig @@ -220,7 +220,6 @@ pub fn build(b: *Builder) !void { } const log_scopes = b.option([]const []const u8, "log", "Which log scopes to enable") orelse &[0][]const u8{}; - const zir_dumps = b.option([]const []const u8, "dump-zir", "Which functions to dump ZIR for before codegen") orelse &[0][]const u8{}; const opt_version_string = b.option([]const u8, "version-string", "Override Zig version string. Default is to find out with git."); const version = if (opt_version_string) |version| version else v: { @@ -277,7 +276,6 @@ pub fn build(b: *Builder) !void { exe.addBuildOption(std.SemanticVersion, "semver", semver); exe.addBuildOption([]const []const u8, "log_scopes", log_scopes); - exe.addBuildOption([]const []const u8, "zir_dumps", zir_dumps); exe.addBuildOption(bool, "enable_tracy", tracy != null); exe.addBuildOption(bool, "is_stage1", is_stage1); if (tracy) |tracy_path| { diff --git a/src/Compilation.zig b/src/Compilation.zig index de115b9b40..a6f39a3154 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1459,10 +1459,10 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor const module = self.bin_file.options.module.?; if (decl.typed_value.most_recent.typed_value.val.castTag(.function)) |payload| { const func = payload.data; - switch (func.analysis) { + switch (func.bits.state) { .queued => module.analyzeFnBody(decl, func) catch |err| switch (err) { error.AnalysisFail => { - assert(func.analysis != .in_progress); + assert(func.bits.state != .in_progress); continue; }, error.OutOfMemory => return error.OutOfMemory, @@ -1471,12 +1471,16 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .sema_failure, .dependency_failure => continue, .success => {}, } - // Here we tack on additional allocations to the Decl's arena. The allocations are - // lifetime annotations in the ZIR. + // Here we tack on additional allocations to the Decl's arena. The allocations + // are lifetime annotations in the ZIR. var decl_arena = decl.typed_value.most_recent.arena.?.promote(module.gpa); defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; log.debug("analyze liveness of {s}\n", .{decl.name}); - try liveness.analyze(module.gpa, &decl_arena.allocator, func.analysis.success); + try liveness.analyze(module.gpa, &decl_arena.allocator, func.data.body); + + if (self.verbose_ir) { + func.dump(module.*); + } } assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); diff --git a/src/Module.zig b/src/Module.zig index 29c19c09a0..db76ecd5db 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -286,23 +286,40 @@ pub const Decl = struct { /// Extern functions do not have this data structure; they are represented by /// the `Decl` only, with a `Value` tag of `extern_fn`. pub const Fn = struct { - /// This memory owned by the Decl's TypedValue.Managed arena allocator. - analysis: union(enum) { + bits: packed struct { + /// Get and set this field via `analysis` and `setAnalysis`. + state: Analysis.Tag, + /// We carry this state into `Fn` instead of leaving it in the AST so that + /// analysis of function calls can happen even on functions whose AST has + /// been unloaded from memory. + is_inline: bool, + unused_bits: u4 = 0, + }, + /// Get and set this data via `analysis` and `setAnalysis`. + data: union { + none: void, + zir: *ZIR, + body: Body, + }, + owner_decl: *Decl, + + pub const Analysis = union(Tag) { queued: *ZIR, in_progress, - /// There will be a corresponding ErrorMsg in Module.failed_decls sema_failure, - /// This Fn might be OK but it depends on another Decl which did not successfully complete - /// semantic analysis. dependency_failure, success: Body, - }, - owner_decl: *Decl, - /// This memory is temporary and points to stack memory for the duration - /// of Fn analysis. - pub const Analysis = struct { - inner_block: Scope.Block, + pub const Tag = enum(u3) { + queued, + in_progress, + /// There will be a corresponding ErrorMsg in Module.failed_decls + sema_failure, + /// This Fn might be OK but it depends on another Decl which did not + /// successfully complete semantic analysis. + dependency_failure, + success, + }; }; /// Contains un-analyzed ZIR instructions generated from Zig source AST. @@ -311,22 +328,37 @@ pub const Fn = struct { arena: std.heap.ArenaAllocator.State, }; - /// For debugging purposes. - pub fn dump(self: *Fn, mod: Module) void { - std.debug.print("Module.Function(name={s}) ", .{self.owner_decl.name}); - switch (self.analysis) { - .queued => { - std.debug.print("queued\n", .{}); + pub fn analysis(self: Fn) Analysis { + return switch (self.bits.state) { + .queued => .{ .queued = self.data.zir }, + .success => .{ .success = self.data.body }, + .in_progress => .in_progress, + .sema_failure => .sema_failure, + .dependency_failure => .dependency_failure, + }; + } + + pub fn setAnalysis(self: *Fn, anal: Analysis) void { + switch (anal) { + .queued => |zir_ptr| { + self.bits.state = .queued; + self.data = .{ .zir = zir_ptr }; }, - .in_progress => { - std.debug.print("in_progress\n", .{}); + .success => |body| { + self.bits.state = .success; + self.data = .{ .body = body }; }, - else => { - std.debug.print("\n", .{}); - zir.dumpFn(mod, self); + .in_progress, .sema_failure, .dependency_failure => { + self.bits.state = anal; + self.data = .{ .none = {} }; }, } } + + /// For debugging purposes. + pub fn dump(self: *Fn, mod: Module) void { + zir.dumpFn(mod, self); + } }; pub const Var = struct { @@ -773,13 +805,33 @@ pub const Scope = struct { instructions: ArrayListUnmanaged(*Inst), /// Points to the arena allocator of DeclAnalysis arena: *Allocator, - label: ?Label = null, + label: Label = Label.none, is_comptime: bool, - pub const Label = struct { - zir_block: *zir.Inst.Block, - results: ArrayListUnmanaged(*Inst), - block_inst: *Inst.Block, + pub const Label = union(enum) { + none, + /// This `Block` maps a block ZIR instruction to the corresponding + /// TZIR instruction for break instruction analysis. + breaking: struct { + zir_block: *zir.Inst.Block, + merges: Merges, + }, + /// This `Block` indicates that an inline function call is happening + /// and return instructions should be analyzed as a break instruction + /// to this TZIR block instruction. + inlining: struct { + /// We use this to count from 0 so that arg instructions know + /// which parameter index they are, without having to store + /// a parameter index with each arg instruction. + param_index: usize, + casted_args: []*Inst, + merges: Merges, + }, + + pub const Merges = struct { + results: ArrayListUnmanaged(*Inst), + block_inst: *Inst.Block, + }; }; /// For debugging purposes. @@ -1189,8 +1241,21 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { break :blk fn_zir; }; + const is_inline = blk: { + if (fn_proto.getExternExportInlineToken()) |maybe_inline_token| { + if (tree.token_ids[maybe_inline_token] == .Keyword_inline) { + break :blk true; + } + } + break :blk false; + }; + new_func.* = .{ - .analysis = .{ .queued = fn_zir }, + .bits = .{ + .state = .queued, + .is_inline = is_inline, + }, + .data = .{ .zir = fn_zir }, .owner_decl = decl, }; fn_payload.* = .{ @@ -1199,11 +1264,16 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; var prev_type_has_bits = false; + var prev_is_inline = false; var type_changed = true; if (decl.typedValueManaged()) |tvm| { prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); type_changed = !tvm.typed_value.ty.eql(fn_type); + if (tvm.typed_value.val.castTag(.function)) |payload| { + const prev_func = payload.data; + prev_is_inline = prev_func.bits.is_inline; + } tvm.deinit(self.gpa); } @@ -1221,18 +1291,26 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { decl.analysis = .complete; decl.generation = self.generation; - if (fn_type.hasCodeGenBits()) { + if (!is_inline and fn_type.hasCodeGenBits()) { // We don't fully codegen the decl until later, but we do need to reserve a global // offset table index for it. This allows us to codegen decls out of dependency order, // increasing how many computations can be done in parallel. try self.comp.bin_file.allocateDeclIndexes(decl); try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); - } else if (prev_type_has_bits) { + } else if (!prev_is_inline and prev_type_has_bits) { self.comp.bin_file.freeDecl(decl); } if (fn_proto.getExternExportInlineToken()) |maybe_export_token| { if (tree.token_ids[maybe_export_token] == .Keyword_export) { + if (is_inline) { + return self.failTok( + &block_scope.base, + maybe_export_token, + "export of inline function", + .{}, + ); + } const export_src = tree.token_locs[maybe_export_token].start; const name_loc = tree.token_locs[fn_proto.getNameToken().?]; const name = tree.tokenSliceLoc(name_loc); @@ -1240,7 +1318,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { try self.analyzeExport(&block_scope.base, export_src, name, decl); } } - return type_changed; + return type_changed or is_inline != prev_is_inline; }, .VarDecl => { const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", ast_node); @@ -1824,15 +1902,15 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { }; defer inner_block.instructions.deinit(self.gpa); - const fn_zir = func.analysis.queued; + const fn_zir = func.data.zir; defer fn_zir.arena.promote(self.gpa).deinit(); - func.analysis = .{ .in_progress = {} }; + func.setAnalysis(.in_progress); log.debug("set {s} to in_progress\n", .{decl.name}); try zir_sema.analyzeBody(self, &inner_block.base, fn_zir.body); const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items); - func.analysis = .{ .success = .{ .instructions = instructions } }; + func.setAnalysis(.{ .success = .{ .instructions = instructions } }); log.debug("set {s} to success\n", .{decl.name}); } @@ -2329,7 +2407,7 @@ pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) Inn self.ensureDeclAnalyzed(decl) catch |err| { if (scope.cast(Scope.Block)) |block| { if (block.func) |func| { - func.analysis = .dependency_failure; + func.setAnalysis(.dependency_failure); } else { block.decl.analysis = .dependency_failure; } @@ -3029,7 +3107,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com .block => { const block = scope.cast(Scope.Block).?; if (block.func) |func| { - func.analysis = .sema_failure; + func.setAnalysis(.sema_failure); } else { block.decl.analysis = .sema_failure; block.decl.generation = self.generation; diff --git a/src/codegen.zig b/src/codegen.zig index 6530b687e5..588c3dec4c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -532,7 +532,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { self.code.items.len += 4; try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.analysis.success); + try self.genBody(self.mod_fn.data.body); const stack_end = self.max_end_stack; if (stack_end > math.maxInt(i32)) @@ -576,7 +576,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }); } else { try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.analysis.success); + try self.genBody(self.mod_fn.data.body); try self.dbgSetEpilogueBegin(); } }, @@ -593,7 +593,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.analysis.success); + try self.genBody(self.mod_fn.data.body); // Backpatch stack offset const stack_end = self.max_end_stack; @@ -638,13 +638,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { writeInt(u32, try self.code.addManyAsArray(4), Instruction.pop(.al, .{ .fp, .pc }).toU32()); } else { try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.analysis.success); + try self.genBody(self.mod_fn.data.body); try self.dbgSetEpilogueBegin(); } }, else => { try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.analysis.success); + try self.genBody(self.mod_fn.data.body); try self.dbgSetEpilogueBegin(); }, } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 684a03eb79..712d663af0 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -275,7 +275,7 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { try writer.writeAll(" {"); const func: *Module.Fn = func_payload.data; - const instructions = func.analysis.success.instructions; + const instructions = func.data.body.instructions; if (instructions.len > 0) { try writer.writeAll("\n"); for (instructions) |inst| { diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index c7ad59f5d1..1eb4f5bc29 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -63,7 +63,7 @@ pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void { // TODO: check for and handle death of instructions const tv = decl.typed_value.most_recent.typed_value; const mod_fn = tv.val.castTag(.function).?.data; - for (mod_fn.analysis.success.instructions) |inst| try genInst(buf, decl, inst); + for (mod_fn.data.body.instructions) |inst| try genInst(buf, decl, inst); // Write 'end' opcode try writer.writeByte(0x0B); diff --git a/src/config.zig.in b/src/config.zig.in index 9d16cf3824..0dbd3f3c91 100644 --- a/src/config.zig.in +++ b/src/config.zig.in @@ -2,7 +2,6 @@ pub const have_llvm = true; pub const version: [:0]const u8 = "@ZIG_VERSION@"; pub const semver = try @import("std").SemanticVersion.parse(version); pub const log_scopes: []const []const u8 = &[_][]const u8{}; -pub const zir_dumps: []const []const u8 = &[_][]const u8{}; pub const enable_tracy = false; pub const is_stage1 = true; pub const skip_non_native = false; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 116d7c9859..d74236f8c1 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2178,16 +2178,6 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { else => false, }; if (is_fn) { - const zir_dumps = if (std.builtin.is_test) &[0][]const u8{} else build_options.zir_dumps; - if (zir_dumps.len != 0) { - for (zir_dumps) |fn_name| { - if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) { - std.debug.print("\n{s}\n", .{decl.name}); - typed_value.val.castTag(.function).?.data.dump(module.*); - } - } - } - // For functions we need to add a prologue to the debug line program. try dbg_line_buffer.ensureCapacity(26); diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index c70fcc5825..11f87d5495 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -936,16 +936,6 @@ pub fn initDeclDebugBuffers( const typed_value = decl.typed_value.most_recent.typed_value; switch (typed_value.ty.zigTypeTag()) { .Fn => { - const zir_dumps = if (std.builtin.is_test) &[0][]const u8{} else build_options.zir_dumps; - if (zir_dumps.len != 0) { - for (zir_dumps) |fn_name| { - if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) { - std.debug.print("\n{}\n", .{decl.name}); - typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*); - } - } - } - // For functions we need to add a prologue to the debug line program. try dbg_line_buffer.ensureCapacity(26); diff --git a/src/llvm_backend.zig b/src/llvm_backend.zig index 51d1a0840e..5814aa7e7e 100644 --- a/src/llvm_backend.zig +++ b/src/llvm_backend.zig @@ -294,7 +294,7 @@ pub const LLVMIRModule = struct { const entry_block = llvm_func.appendBasicBlock("Entry"); self.builder.positionBuilderAtEnd(entry_block); - const instructions = func.analysis.success.instructions; + const instructions = func.data.body.instructions; for (instructions) |inst| { switch (inst.tag) { .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?), diff --git a/src/zir.zig b/src/zir.zig index 043c54faf0..64b74f24d9 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -793,7 +793,9 @@ pub const Inst = struct { fn_type: *Inst, body: Module.Body, }, - kw_args: struct {}, + kw_args: struct { + is_inline: bool = false, + }, }; pub const FnType = struct { @@ -1847,83 +1849,258 @@ pub fn emit(allocator: *Allocator, old_module: *IrModule) !Module { /// For debugging purposes, prints a function representation to stderr. pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void { const allocator = old_module.gpa; - var ctx: EmitZIR = .{ + var ctx: DumpTzir = .{ .allocator = allocator, - .decls = .{}, .arena = std.heap.ArenaAllocator.init(allocator), .old_module = &old_module, - .next_auto_name = 0, - .names = std.StringArrayHashMap(void).init(allocator), - .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), - .indent = 0, - .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), - .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator), - .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), - .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), + .module_fn = module_fn, + .indent = 2, + .inst_table = DumpTzir.InstTable.init(allocator), + .partial_inst_table = DumpTzir.InstTable.init(allocator), + .const_table = DumpTzir.InstTable.init(allocator), }; - defer ctx.metadata.deinit(); - defer ctx.body_metadata.deinit(); - defer ctx.block_table.deinit(); - defer ctx.loop_table.deinit(); - defer ctx.decls.deinit(allocator); - defer ctx.names.deinit(); - defer ctx.primitive_table.deinit(); + defer ctx.inst_table.deinit(); + defer ctx.partial_inst_table.deinit(); + defer ctx.const_table.deinit(); defer ctx.arena.deinit(); - const fn_ty = module_fn.owner_decl.typed_value.most_recent.typed_value.ty; - _ = ctx.emitFn(module_fn, 0, fn_ty) catch |err| { - std.debug.print("unable to dump function: {s}\n", .{@errorName(err)}); - return; - }; - var module = Module{ - .decls = ctx.decls.items, - .arena = ctx.arena, - .metadata = ctx.metadata, - .body_metadata = ctx.body_metadata, - }; - - module.dump(); + switch (module_fn.analysis()) { + .queued => std.debug.print("(queued)", .{}), + .in_progress => std.debug.print("(in_progress)", .{}), + .sema_failure => std.debug.print("(sema_failure)", .{}), + .dependency_failure => std.debug.print("(dependency_failure)", .{}), + .success => |body| { + ctx.dump(body, std.io.getStdErr().writer()) catch @panic("failed to dump TZIR"); + }, + } } -/// For debugging purposes, prints a function representation to stderr. -pub fn dumpBlock(old_module: IrModule, module_block: *IrModule.Scope.Block) void { - const allocator = old_module.gpa; - var ctx: EmitZIR = .{ - .allocator = allocator, - .decls = .{}, - .arena = std.heap.ArenaAllocator.init(allocator), - .old_module = &old_module, - .next_auto_name = 0, - .names = std.StringArrayHashMap(void).init(allocator), - .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), - .indent = 0, - .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), - .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator), - .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), - .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), - }; - defer ctx.metadata.deinit(); - defer ctx.body_metadata.deinit(); - defer ctx.block_table.deinit(); - defer ctx.loop_table.deinit(); - defer ctx.decls.deinit(allocator); - defer ctx.names.deinit(); - defer ctx.primitive_table.deinit(); - defer ctx.arena.deinit(); +const DumpTzir = struct { + allocator: *Allocator, + arena: std.heap.ArenaAllocator, + old_module: *const IrModule, + module_fn: *IrModule.Fn, + indent: usize, + inst_table: InstTable, + partial_inst_table: InstTable, + const_table: InstTable, + next_index: usize = 0, + next_partial_index: usize = 0, + next_const_index: usize = 0, + + const InstTable = std.AutoArrayHashMap(*ir.Inst, usize); + + fn dump(dtz: *DumpTzir, body: ir.Body, writer: std.fs.File.Writer) !void { + // First pass to pre-populate the table so that we can show even invalid references. + // Must iterate the same order we iterate the second time. + // We also look for constants and put them in the const_table. + for (body.instructions) |inst| { + try dtz.inst_table.put(inst, dtz.next_index); + dtz.next_index += 1; + switch (inst.tag) { + .alloc, + .retvoid, + .unreach, + .breakpoint, + .dbg_stmt, + => {}, - _ = ctx.emitBlock(module_block, 0) catch |err| { - std.debug.print("unable to dump function: {}\n", .{err}); - return; - }; - var module = Module{ - .decls = ctx.decls.items, - .arena = ctx.arena, - .metadata = ctx.metadata, - .body_metadata = ctx.body_metadata, - }; + .ref, + .ret, + .bitcast, + .not, + .isnonnull, + .isnull, + .iserr, + .ptrtoint, + .floatcast, + .intcast, + .load, + .unwrap_optional, + .wrap_optional, + => { + const un_op = inst.cast(ir.Inst.UnOp).?; + try dtz.findConst(un_op.operand); + }, - module.dump(); -} + .add, + .sub, + .cmp_lt, + .cmp_lte, + .cmp_eq, + .cmp_gte, + .cmp_gt, + .cmp_neq, + .store, + .booland, + .boolor, + .bitand, + .bitor, + .xor, + => { + const bin_op = inst.cast(ir.Inst.BinOp).?; + try dtz.findConst(bin_op.lhs); + try dtz.findConst(bin_op.rhs); + }, + + .arg => {}, + + // TODO fill out this debug printing + .assembly, + .block, + .br, + .brvoid, + .call, + .condbr, + .constant, + .loop, + .varptr, + .switchbr, + => {}, + } + } + + std.debug.print("Module.Function(name={s}):\n", .{dtz.module_fn.owner_decl.name}); + + for (dtz.const_table.items()) |entry| { + const constant = entry.key.castTag(.constant).?; + try writer.print(" @{d}: {} = {};\n", .{ + entry.value, constant.base.ty, constant.val, + }); + } + + return dtz.dumpBody(body, writer); + } + + fn dumpBody(dtz: *DumpTzir, body: ir.Body, writer: std.fs.File.Writer) !void { + for (body.instructions) |inst| { + const my_index = dtz.next_partial_index; + try dtz.partial_inst_table.put(inst, my_index); + dtz.next_partial_index += 1; + + try writer.writeByteNTimes(' ', dtz.indent); + try writer.print("%{d}: {} = {s}(", .{ + my_index, inst.ty, @tagName(inst.tag), + }); + switch (inst.tag) { + .alloc, + .retvoid, + .unreach, + .breakpoint, + .dbg_stmt, + => try writer.writeAll(")\n"), + + .ref, + .ret, + .bitcast, + .not, + .isnonnull, + .isnull, + .iserr, + .ptrtoint, + .floatcast, + .intcast, + .load, + .unwrap_optional, + .wrap_optional, + => { + const un_op = inst.cast(ir.Inst.UnOp).?; + if (dtz.partial_inst_table.get(un_op.operand)) |operand_index| { + try writer.print("%{d})\n", .{operand_index}); + } else if (dtz.const_table.get(un_op.operand)) |operand_index| { + try writer.print("@{d})\n", .{operand_index}); + } else if (dtz.inst_table.get(un_op.operand)) |operand_index| { + try writer.print("%{d}) // Instruction does not dominate all uses!\n", .{ + operand_index, + }); + } else { + try writer.writeAll("!BADREF!)\n"); + } + }, + + .add, + .sub, + .cmp_lt, + .cmp_lte, + .cmp_eq, + .cmp_gte, + .cmp_gt, + .cmp_neq, + .store, + .booland, + .boolor, + .bitand, + .bitor, + .xor, + => { + var lhs_kinky: ?usize = null; + var rhs_kinky: ?usize = null; + + const bin_op = inst.cast(ir.Inst.BinOp).?; + if (dtz.partial_inst_table.get(bin_op.lhs)) |operand_index| { + try writer.print("%{d}, ", .{operand_index}); + } else if (dtz.const_table.get(bin_op.lhs)) |operand_index| { + try writer.print("@{d}, ", .{operand_index}); + } else if (dtz.inst_table.get(bin_op.lhs)) |operand_index| { + lhs_kinky = operand_index; + try writer.print("%{d}, ", .{operand_index}); + } else { + try writer.writeAll("!BADREF!, "); + } + if (dtz.partial_inst_table.get(bin_op.rhs)) |operand_index| { + try writer.print("%{d}", .{operand_index}); + } else if (dtz.const_table.get(bin_op.rhs)) |operand_index| { + try writer.print("@{d}", .{operand_index}); + } else if (dtz.inst_table.get(bin_op.rhs)) |operand_index| { + rhs_kinky = operand_index; + try writer.print("%{d}", .{operand_index}); + } else { + try writer.writeAll("!BADREF!"); + } + if (lhs_kinky != null or rhs_kinky != null) { + try writer.writeAll(") // Instruction does not dominate all uses!"); + if (lhs_kinky) |lhs| { + try writer.print(" %{d}", .{lhs}); + } + if (rhs_kinky) |rhs| { + try writer.print(" %{d}", .{rhs}); + } + try writer.writeAll("\n"); + } else { + try writer.writeAll(")\n"); + } + }, + + .arg => { + const arg = inst.castTag(.arg).?; + try writer.print("{s})\n", .{arg.name}); + }, + + // TODO fill out this debug printing + .assembly, + .block, + .br, + .brvoid, + .call, + .condbr, + .constant, + .loop, + .varptr, + .switchbr, + => { + try writer.writeAll("!TODO!)\n"); + }, + } + } + } + + fn findConst(dtz: *DumpTzir, operand: *ir.Inst) !void { + if (operand.tag == .constant) { + try dtz.const_table.put(operand, dtz.next_const_index); + dtz.next_const_index += 1; + } + } +}; const EmitZIR = struct { allocator: *Allocator, @@ -2105,36 +2282,6 @@ const EmitZIR = struct { return &declref_inst.base; } - fn emitBlock(self: *EmitZIR, module_block: *IrModule.Scope.Block, src: usize) Allocator.Error!*Decl { - var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); - defer inst_table.deinit(); - - var instructions = std.ArrayList(*Inst).init(self.allocator); - defer instructions.deinit(); - - const body: ir.Body = .{ .instructions = module_block.instructions.items }; - try self.emitBody(body, &inst_table, &instructions); - - const fn_type = try self.emitType(src, Type.initTag(.void)); - - const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); - mem.copy(*Inst, arena_instrs, instructions.items); - - const fn_inst = try self.arena.allocator.create(Inst.Fn); - fn_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Fn.base_tag, - }, - .positionals = .{ - .fn_type = fn_type.inst, - .body = .{ .instructions = arena_instrs }, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&fn_inst.base); - } - fn emitFn(self: *EmitZIR, module_fn: *IrModule.Fn, src: usize, ty: Type) Allocator.Error!*Decl { var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); defer inst_table.deinit(); @@ -2142,7 +2289,7 @@ const EmitZIR = struct { var instructions = std.ArrayList(*Inst).init(self.allocator); defer instructions.deinit(); - switch (module_fn.analysis) { + switch (module_fn.analysis()) { .queued => unreachable, .in_progress => unreachable, .success => |body| { @@ -2224,7 +2371,9 @@ const EmitZIR = struct { .fn_type = fn_type.inst, .body = .{ .instructions = arena_instrs }, }, - .kw_args = .{}, + .kw_args = .{ + .is_inline = module_fn.bits.is_inline, + }, }; return self.emitUnnamedDecl(&fn_inst.base); } diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 96a19f20f4..9365996bb6 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -577,7 +577,15 @@ fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) In } fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst { - const b = try mod.requireRuntimeBlock(scope, inst.base.src); + const b = try mod.requireFunctionBlock(scope, inst.base.src); + switch (b.label) { + .none, .breaking => {}, + .inlining => |*inlining| { + const param_index = inlining.param_index; + inlining.param_index += 1; + return inlining.casted_args[param_index]; + }, + } const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; const param_index = b.instructions.items.len; const param_count = fn_ty.fnParamLen(); @@ -636,7 +644,7 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c .decl = parent_block.decl, .instructions = .{}, .arena = parent_block.arena, - .label = null, + .label = .none, .is_comptime = parent_block.is_comptime or is_comptime, }; defer child_block.instructions.deinit(mod.gpa); @@ -674,41 +682,56 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt .decl = parent_block.decl, .instructions = .{}, .arena = parent_block.arena, - // TODO @as here is working around a stage1 miscompilation bug :( - .label = @as(?Scope.Block.Label, Scope.Block.Label{ - .zir_block = inst, - .results = .{}, - .block_inst = block_inst, - }), + .label = Scope.Block.Label{ + .breaking = .{ + .zir_block = inst, + .merges = .{ + .results = .{}, + .block_inst = block_inst, + }, + }, + }, .is_comptime = is_comptime or parent_block.is_comptime, }; - const label = &child_block.label.?; + const merges = &child_block.label.breaking.merges; defer child_block.instructions.deinit(mod.gpa); - defer label.results.deinit(mod.gpa); + defer merges.results.deinit(mod.gpa); try analyzeBody(mod, &child_block.base, inst.positionals.body); + return analyzeBlockBody(mod, scope, &child_block, merges); +} + +fn analyzeBlockBody( + mod: *Module, + scope: *Scope, + child_block: *Scope.Block, + merges: *Scope.Block.Label.Merges, +) InnerError!*Inst { + const parent_block = scope.cast(Scope.Block).?; + // Blocks must terminate with noreturn instruction. assert(child_block.instructions.items.len != 0); assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); - if (label.results.items.len == 0) { - // No need for a block instruction. We can put the new instructions directly into the parent block. + if (merges.results.items.len == 0) { + // No need for a block instruction. We can put the new instructions + // directly into the parent block. const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items); try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); return copied_instructions[copied_instructions.len - 1]; } - if (label.results.items.len == 1) { + if (merges.results.items.len == 1) { const last_inst_index = child_block.instructions.items.len - 1; const last_inst = child_block.instructions.items[last_inst_index]; if (last_inst.breakBlock()) |br_block| { - if (br_block == block_inst) { + if (br_block == merges.block_inst) { // No need for a block instruction. We can put the new instructions directly into the parent block. // Here we omit the break instruction. const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items[0..last_inst_index]); try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); - return label.results.items[0]; + return merges.results.items[0]; } } } @@ -717,10 +740,10 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt // Need to set the type and emit the Block instruction. This allows machine code generation // to emit a jump instruction to after the block when it encounters the break. - try parent_block.instructions.append(mod.gpa, &block_inst.base); - block_inst.base.ty = try mod.resolvePeerTypes(scope, label.results.items); - block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; - return &block_inst.base; + try parent_block.instructions.append(mod.gpa, &merges.block_inst.base); + merges.block_inst.base.ty = try mod.resolvePeerTypes(scope, merges.results.items); + merges.block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; + return &merges.block_inst.base; } fn analyzeInstBreakpoint(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { @@ -829,14 +852,32 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError const ret_type = func.ty.fnReturnType(); const b = try mod.requireFunctionBlock(scope, inst.base.src); - if (b.is_comptime) { - const fn_val = try mod.resolveConstValue(scope, func); - const module_fn = switch (fn_val.tag()) { - .function => fn_val.castTag(.function).?.data, - .extern_fn => return mod.fail(scope, inst.base.src, "comptime call of extern function", .{}), + const is_comptime_call = b.is_comptime or inst.kw_args.modifier == .compile_time; + const is_inline_call = is_comptime_call or inst.kw_args.modifier == .always_inline or blk: { + // This logic will get simplified by + // https://github.com/ziglang/zig/issues/6429 + if (try mod.resolveDefinedValue(scope, func)) |func_val| { + const module_fn = switch (func_val.tag()) { + .function => func_val.castTag(.function).?.data, + else => break :blk false, + }; + break :blk module_fn.bits.is_inline; + } + break :blk false; + }; + if (is_inline_call) { + const func_val = try mod.resolveConstValue(scope, func); + const module_fn = switch (func_val.tag()) { + .function => func_val.castTag(.function).?.data, + .extern_fn => return mod.fail(scope, inst.base.src, "{s} call of extern function", .{ + @as([]const u8, if (is_comptime_call) "comptime" else "inline"), + }), else => unreachable, }; const callee_decl = module_fn.owner_decl; + // TODO: De-duplicate this with the code in Module.zig that generates + // ZIR for the same function and re-use the same ZIR for runtime function + // generation and for inline/comptime calls. const callee_file_scope = callee_decl.getFileScope(); const tree = mod.getAstTree(callee_file_scope) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -859,23 +900,31 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError }; defer gen_scope.instructions.deinit(mod.gpa); - // Add a const instruction for each parameter. + // We need an instruction for each parameter, and they must be first in the body. + try gen_scope.instructions.resize(mod.gpa, fn_proto.params_len); var params_scope = &gen_scope.base; for (fn_proto.params()) |param, i| { const name_token = param.name_token.?; const src = tree.token_locs[name_token].start; const param_name = try mod.identifierTokenString(scope, name_token); - const arg_val = try mod.resolveConstValue(scope, casted_args[i]); - const arg = try astgen.addZIRInstConst(mod, params_scope, src, .{ - .ty = casted_args[i].ty, - .val = arg_val, - }); + const arg = try call_arena.allocator.create(zir.Inst.Arg); + arg.* = .{ + .base = .{ + .tag = .arg, + .src = src, + }, + .positionals = .{ + .name = param_name, + }, + .kw_args = .{}, + }; + gen_scope.instructions.items[i] = &arg.base; const sub_scope = try call_arena.allocator.create(Scope.LocalVal); sub_scope.* = .{ .parent = params_scope, .gen_zir = &gen_scope, .name = param_name, - .inst = arg, + .inst = &arg.base, }; params_scope = &sub_scope.base; } @@ -896,42 +945,52 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError zir.dumpZir(mod.gpa, "fn_body_callee", callee_decl.name, gen_scope.instructions.items) catch {}; } - // Analyze the ZIR. - var inner_block: Scope.Block = .{ + // Analyze the ZIR. The same ZIR gets analyzed into a runtime function + // or an inlined call depending on what union tag the `label` field is + // set to in the `Scope.Block`. + // This block instruction will be used to capture the return value from the + // inlined function. + const block_inst = try scope.arena().create(Inst.Block); + block_inst.* = .{ + .base = .{ + .tag = Inst.Block.base_tag, + .ty = ret_type, + .src = inst.base.src, + }, + .body = undefined, + }; + var child_block: Scope.Block = .{ .parent = null, .func = module_fn, - .decl = callee_decl, + // Note that we pass the caller's Decl, not the callee. This causes + // compile errors to be attached (correctly) to the caller's Decl. + .decl = scope.decl().?, .instructions = .{}, - .arena = &call_arena.allocator, - .is_comptime = true, + .arena = scope.arena(), + .label = Scope.Block.Label{ + .inlining = .{ + .param_index = 0, + .casted_args = casted_args, + .merges = .{ + .results = .{}, + .block_inst = block_inst, + }, + }, + }, + .is_comptime = is_comptime_call, }; - defer inner_block.instructions.deinit(mod.gpa); + const merges = &child_block.label.inlining.merges; + + defer child_block.instructions.deinit(mod.gpa); + defer merges.results.deinit(mod.gpa); - // TODO make sure compile errors that happen from this analyzeBody are reported correctly - // and attach to the caller Decl not the callee. - try analyzeBody(mod, &inner_block.base, .{ + // This will have return instructions analyzed as break instructions to + // the block_inst above. + try analyzeBody(mod, &child_block.base, .{ .instructions = gen_scope.instructions.items, }); - if (mod.comp.verbose_ir) { - inner_block.dump(mod.*); - } - - assert(inner_block.instructions.items.len == 1); - const only_inst = inner_block.instructions.items[0]; - switch (only_inst.tag) { - .ret => { - const ret_inst = only_inst.castTag(.ret).?; - const operand = ret_inst.operand; - const callee_arena = scope.arena(); - return mod.constInst(scope, inst.base.src, .{ - .ty = try operand.ty.copy(callee_arena), - .val = try operand.value().?.copy(callee_arena), - }); - }, - .retvoid => return mod.constVoid(scope, inst.base.src), - else => unreachable, - } + return analyzeBlockBody(mod, scope, &child_block, merges); } return mod.addCall(b, inst.base.src, ret_type, func, casted_args); @@ -954,7 +1013,11 @@ fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError! }; const new_func = try scope.arena().create(Module.Fn); new_func.* = .{ - .analysis = .{ .queued = fn_zir }, + .bits = .{ + .state = .queued, + .is_inline = fn_inst.kw_args.is_inline, + }, + .data = .{ .zir = fn_zir }, .owner_decl = scope.decl().?, }; return mod.constInst(scope, fn_inst.base.src, .{ @@ -2020,21 +2083,41 @@ fn analyzeInstUnreachable( fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const operand = try resolveInst(mod, scope, inst.positionals.operand); const b = try mod.requireFunctionBlock(scope, inst.base.src); - return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand); + + switch (b.label) { + .inlining => |*inlining| { + // We are inlining a function call; rewrite the `ret` as a `break`. + try inlining.merges.results.append(mod.gpa, operand); + return mod.addBr(b, inst.base.src, inlining.merges.block_inst, operand); + }, + .none, .breaking => { + return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand); + }, + } } fn analyzeInstRetVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { const b = try mod.requireFunctionBlock(scope, inst.base.src); - if (b.func) |func| { - // Need to emit a compile error if returning void is not allowed. - const void_inst = try mod.constVoid(scope, inst.base.src); - const fn_ty = func.owner_decl.typed_value.most_recent.typed_value.ty; - const casted_void = try mod.coerce(scope, fn_ty.fnReturnType(), void_inst); - if (casted_void.ty.zigTypeTag() != .Void) { - return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, casted_void); - } + switch (b.label) { + .inlining => |*inlining| { + // We are inlining a function call; rewrite the `retvoid` as a `breakvoid`. + const void_inst = try mod.constVoid(scope, inst.base.src); + try inlining.merges.results.append(mod.gpa, void_inst); + return mod.addBr(b, inst.base.src, inlining.merges.block_inst, void_inst); + }, + .none, .breaking => { + if (b.func) |func| { + // Need to emit a compile error if returning void is not allowed. + const void_inst = try mod.constVoid(scope, inst.base.src); + const fn_ty = func.owner_decl.typed_value.most_recent.typed_value.ty; + const casted_void = try mod.coerce(scope, fn_ty.fnReturnType(), void_inst); + if (casted_void.ty.zigTypeTag() != .Void) { + return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, casted_void); + } + } + return mod.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid); + }, } - return mod.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid); } fn floatOpAllowed(tag: zir.Inst.Tag) bool { @@ -2054,12 +2137,16 @@ fn analyzeBreak( ) InnerError!*Inst { var opt_block = scope.cast(Scope.Block); while (opt_block) |block| { - if (block.label) |*label| { - if (label.zir_block == zir_block) { - try label.results.append(mod.gpa, operand); - const b = try mod.requireRuntimeBlock(scope, src); - return mod.addBr(b, src, label.block_inst, operand); - } + switch (block.label) { + .none => {}, + .breaking => |*label| { + if (label.zir_block == zir_block) { + try label.merges.results.append(mod.gpa, operand); + const b = try mod.requireFunctionBlock(scope, src); + return mod.addBr(b, src, label.merges.block_inst, operand); + } + }, + .inlining => unreachable, // Invalid `break` ZIR inside inline function call. } opt_block = block.parent; } else unreachable; diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index da4038e792..c29e636cd4 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -30,7 +30,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@unnamed$7 = fntype([], @void, cc=C) \\@entry = fn(@unnamed$7, { \\ %0 = returnvoid() ; deaths=0b1000000000000000 - \\}) + \\}, is_inline=0) \\ ); ctx.transformZIR("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, @@ -78,7 +78,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@unnamed$6 = fntype([], @void, cc=C) \\@entry = fn(@unnamed$6, { \\ %0 = returnvoid() ; deaths=0b1000000000000000 - \\}) + \\}, is_inline=0) \\@entry__anon_1 = str("2\x08\x01\n") \\@9 = declref("9__anon_0") \\@9__anon_0 = str("entry") @@ -123,17 +123,17 @@ pub fn addCases(ctx: *TestContext) !void { \\@entry = fn(@unnamed$7, { \\ %0 = call(@a, [], modifier=auto) ; deaths=0b1000000000000001 \\ %1 = returnvoid() ; deaths=0b1000000000000000 - \\}) + \\}, is_inline=0) \\@unnamed$9 = fntype([], @void, cc=C) \\@a = fn(@unnamed$9, { \\ %0 = call(@b, [], modifier=auto) ; deaths=0b1000000000000001 \\ %1 = returnvoid() ; deaths=0b1000000000000000 - \\}) + \\}, is_inline=0) \\@unnamed$11 = fntype([], @void, cc=C) \\@b = fn(@unnamed$11, { \\ %0 = call(@a, [], modifier=auto) ; deaths=0b1000000000000001 \\ %1 = returnvoid() ; deaths=0b1000000000000000 - \\}) + \\}, is_inline=0) \\ ); // Now we introduce a compile error @@ -203,7 +203,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@unnamed$7 = fntype([], @void, cc=C) \\@entry = fn(@unnamed$7, { \\ %0 = returnvoid() ; deaths=0b1000000000000000 - \\}) + \\}, is_inline=0) \\ ); } -- cgit v1.2.3 From 006e7f68056af62ae7713d7ef228841d11874735 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 2 Jan 2021 13:40:23 -0700 Subject: stage2: re-use ZIR for comptime and inline calls Instead of freeing ZIR after semantic analysis, we keep it around so that it can be used for comptime calls, inline calls, and generic function calls. ZIR memory is now managed by the Decl arena. Debug dump() functions are conditionally compiled; only available in Debug builds of the compiler. Add a test for an inline function call. --- src/Compilation.zig | 9 ++-- src/Module.zig | 136 +++++++++++++++------------------------------------ src/codegen.zig | 10 ++-- src/codegen/c.zig | 2 +- src/codegen/wasm.zig | 2 +- src/llvm_backend.zig | 2 +- src/zir.zig | 17 ++++--- src/zir_sema.zig | 99 ++----------------------------------- test/stage2/test.zig | 25 ++++++++++ 9 files changed, 93 insertions(+), 209 deletions(-) (limited to 'src/codegen') diff --git a/src/Compilation.zig b/src/Compilation.zig index a6f39a3154..9a06aee561 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1459,15 +1459,16 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor const module = self.bin_file.options.module.?; if (decl.typed_value.most_recent.typed_value.val.castTag(.function)) |payload| { const func = payload.data; - switch (func.bits.state) { + switch (func.state) { .queued => module.analyzeFnBody(decl, func) catch |err| switch (err) { error.AnalysisFail => { - assert(func.bits.state != .in_progress); + assert(func.state != .in_progress); continue; }, error.OutOfMemory => return error.OutOfMemory, }, .in_progress => unreachable, + .inline_only => unreachable, // don't queue work for this .sema_failure, .dependency_failure => continue, .success => {}, } @@ -1476,9 +1477,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor var decl_arena = decl.typed_value.most_recent.arena.?.promote(module.gpa); defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; log.debug("analyze liveness of {s}\n", .{decl.name}); - try liveness.analyze(module.gpa, &decl_arena.allocator, func.data.body); + try liveness.analyze(module.gpa, &decl_arena.allocator, func.body); - if (self.verbose_ir) { + if (std.builtin.mode == .Debug and self.verbose_ir) { func.dump(module.*); } } diff --git a/src/Module.zig b/src/Module.zig index db76ecd5db..be6ca0df63 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -286,75 +286,29 @@ pub const Decl = struct { /// Extern functions do not have this data structure; they are represented by /// the `Decl` only, with a `Value` tag of `extern_fn`. pub const Fn = struct { - bits: packed struct { - /// Get and set this field via `analysis` and `setAnalysis`. - state: Analysis.Tag, - /// We carry this state into `Fn` instead of leaving it in the AST so that - /// analysis of function calls can happen even on functions whose AST has - /// been unloaded from memory. - is_inline: bool, - unused_bits: u4 = 0, - }, - /// Get and set this data via `analysis` and `setAnalysis`. - data: union { - none: void, - zir: *ZIR, - body: Body, - }, owner_decl: *Decl, - - pub const Analysis = union(Tag) { - queued: *ZIR, + /// Contains un-analyzed ZIR instructions generated from Zig source AST. + /// Even after we finish analysis, the ZIR is kept in memory, so that + /// comptime and inline function calls can happen. + zir: zir.Module.Body, + /// undefined unless analysis state is `success`. + body: Body, + state: Analysis, + + pub const Analysis = enum { + queued, + /// This function intentionally only has ZIR generated because it is marked + /// inline, which means no runtime version of the function will be generated. + inline_only, in_progress, + /// There will be a corresponding ErrorMsg in Module.failed_decls sema_failure, + /// This Fn might be OK but it depends on another Decl which did not + /// successfully complete semantic analysis. dependency_failure, - success: Body, - - pub const Tag = enum(u3) { - queued, - in_progress, - /// There will be a corresponding ErrorMsg in Module.failed_decls - sema_failure, - /// This Fn might be OK but it depends on another Decl which did not - /// successfully complete semantic analysis. - dependency_failure, - success, - }; + success, }; - /// Contains un-analyzed ZIR instructions generated from Zig source AST. - pub const ZIR = struct { - body: zir.Module.Body, - arena: std.heap.ArenaAllocator.State, - }; - - pub fn analysis(self: Fn) Analysis { - return switch (self.bits.state) { - .queued => .{ .queued = self.data.zir }, - .success => .{ .success = self.data.body }, - .in_progress => .in_progress, - .sema_failure => .sema_failure, - .dependency_failure => .dependency_failure, - }; - } - - pub fn setAnalysis(self: *Fn, anal: Analysis) void { - switch (anal) { - .queued => |zir_ptr| { - self.bits.state = .queued; - self.data = .{ .zir = zir_ptr }; - }, - .success => |body| { - self.bits.state = .success; - self.data = .{ .body = body }; - }, - .in_progress, .sema_failure, .dependency_failure => { - self.bits.state = anal; - self.data = .{ .none = {} }; - }, - } - } - /// For debugging purposes. pub fn dump(self: *Fn, mod: Module) void { zir.dumpFn(mod, self); @@ -1124,7 +1078,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .param_types = param_types, }, .{}); - if (self.comp.verbose_ir) { + if (std.builtin.mode == .Debug and self.comp.verbose_ir) { zir.dumpZir(self.gpa, "fn_type", decl.name, fn_type_scope.instructions.items) catch {}; } @@ -1175,14 +1129,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const new_func = try decl_arena.allocator.create(Fn); const fn_payload = try decl_arena.allocator.create(Value.Payload.Function); - const fn_zir = blk: { - // This scope's arena memory is discarded after the ZIR generation - // pass completes, and semantic analysis of it completes. - var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa); - errdefer gen_scope_arena.deinit(); + const fn_zir: zir.Module.Body = blk: { + // We put the ZIR inside the Decl arena. var gen_scope: Scope.GenZIR = .{ .decl = decl, - .arena = &gen_scope_arena.allocator, + .arena = &decl_arena.allocator, .parent = decl.scope, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1194,7 +1145,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const name_token = param.name_token.?; const src = tree.token_locs[name_token].start; const param_name = try self.identifierTokenString(&gen_scope.base, name_token); - const arg = try gen_scope_arena.allocator.create(zir.Inst.Arg); + const arg = try decl_arena.allocator.create(zir.Inst.Arg); arg.* = .{ .base = .{ .tag = .arg, @@ -1206,7 +1157,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .kw_args = .{}, }; gen_scope.instructions.items[i] = &arg.base; - const sub_scope = try gen_scope_arena.allocator.create(Scope.LocalVal); + const sub_scope = try decl_arena.allocator.create(Scope.LocalVal); sub_scope.* = .{ .parent = params_scope, .gen_zir = &gen_scope, @@ -1227,18 +1178,13 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid); } - if (self.comp.verbose_ir) { + if (std.builtin.mode == .Debug and self.comp.verbose_ir) { zir.dumpZir(self.gpa, "fn_body", decl.name, gen_scope.instructions.items) catch {}; } - const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); - fn_zir.* = .{ - .body = .{ - .instructions = try gen_scope.arena.dupe(*zir.Inst, gen_scope.instructions.items), - }, - .arena = gen_scope_arena.state, + break :blk .{ + .instructions = try gen_scope.arena.dupe(*zir.Inst, gen_scope.instructions.items), }; - break :blk fn_zir; }; const is_inline = blk: { @@ -1249,13 +1195,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { } break :blk false; }; + const anal_state = ([2]Fn.Analysis{ .queued, .inline_only })[@boolToInt(is_inline)]; new_func.* = .{ - .bits = .{ - .state = .queued, - .is_inline = is_inline, - }, - .data = .{ .zir = fn_zir }, + .state = anal_state, + .zir = fn_zir, + .body = undefined, .owner_decl = decl, }; fn_payload.* = .{ @@ -1272,7 +1217,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { type_changed = !tvm.typed_value.ty.eql(fn_type); if (tvm.typed_value.val.castTag(.function)) |payload| { const prev_func = payload.data; - prev_is_inline = prev_func.bits.is_inline; + prev_is_inline = prev_func.state == .inline_only; } tvm.deinit(self.gpa); @@ -1391,7 +1336,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const src = tree.token_locs[init_node.firstToken()].start; const init_inst = try astgen.expr(self, &gen_scope.base, init_result_loc, init_node); - if (self.comp.verbose_ir) { + if (std.builtin.mode == .Debug and self.comp.verbose_ir) { zir.dumpZir(self.gpa, "var_init", decl.name, gen_scope.instructions.items) catch {}; } @@ -1435,7 +1380,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .val = Value.initTag(.type_type), }); const var_type = try astgen.expr(self, &type_scope.base, .{ .ty = type_type }, type_node); - if (self.comp.verbose_ir) { + if (std.builtin.mode == .Debug and self.comp.verbose_ir) { zir.dumpZir(self.gpa, "var_type", decl.name, type_scope.instructions.items) catch {}; } @@ -1511,7 +1456,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { defer gen_scope.instructions.deinit(self.gpa); _ = try astgen.comptimeExpr(self, &gen_scope.base, .none, comptime_decl.expr); - if (self.comp.verbose_ir) { + if (std.builtin.mode == .Debug and self.comp.verbose_ir) { zir.dumpZir(self.gpa, "comptime_block", decl.name, gen_scope.instructions.items) catch {}; } @@ -1902,15 +1847,14 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { }; defer inner_block.instructions.deinit(self.gpa); - const fn_zir = func.data.zir; - defer fn_zir.arena.promote(self.gpa).deinit(); - func.setAnalysis(.in_progress); + func.state = .in_progress; log.debug("set {s} to in_progress\n", .{decl.name}); - try zir_sema.analyzeBody(self, &inner_block.base, fn_zir.body); + try zir_sema.analyzeBody(self, &inner_block.base, func.zir); const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items); - func.setAnalysis(.{ .success = .{ .instructions = instructions } }); + func.state = .success; + func.body = .{ .instructions = instructions }; log.debug("set {s} to success\n", .{decl.name}); } @@ -2407,7 +2351,7 @@ pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) Inn self.ensureDeclAnalyzed(decl) catch |err| { if (scope.cast(Scope.Block)) |block| { if (block.func) |func| { - func.setAnalysis(.dependency_failure); + func.state = .dependency_failure; } else { block.decl.analysis = .dependency_failure; } @@ -3107,7 +3051,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com .block => { const block = scope.cast(Scope.Block).?; if (block.func) |func| { - func.setAnalysis(.sema_failure); + func.state = .sema_failure; } else { block.decl.analysis = .sema_failure; block.decl.generation = self.generation; diff --git a/src/codegen.zig b/src/codegen.zig index 588c3dec4c..58be73a31c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -532,7 +532,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { self.code.items.len += 4; try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.data.body); + try self.genBody(self.mod_fn.body); const stack_end = self.max_end_stack; if (stack_end > math.maxInt(i32)) @@ -576,7 +576,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }); } else { try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.data.body); + try self.genBody(self.mod_fn.body); try self.dbgSetEpilogueBegin(); } }, @@ -593,7 +593,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.data.body); + try self.genBody(self.mod_fn.body); // Backpatch stack offset const stack_end = self.max_end_stack; @@ -638,13 +638,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { writeInt(u32, try self.code.addManyAsArray(4), Instruction.pop(.al, .{ .fp, .pc }).toU32()); } else { try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.data.body); + try self.genBody(self.mod_fn.body); try self.dbgSetEpilogueBegin(); } }, else => { try self.dbgSetPrologueEnd(); - try self.genBody(self.mod_fn.data.body); + try self.genBody(self.mod_fn.body); try self.dbgSetEpilogueBegin(); }, } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 712d663af0..1a89e22d48 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -275,7 +275,7 @@ pub fn generate(file: *C, module: *Module, decl: *Decl) !void { try writer.writeAll(" {"); const func: *Module.Fn = func_payload.data; - const instructions = func.data.body.instructions; + const instructions = func.body.instructions; if (instructions.len > 0) { try writer.writeAll("\n"); for (instructions) |inst| { diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 1eb4f5bc29..036243dcca 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -63,7 +63,7 @@ pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void { // TODO: check for and handle death of instructions const tv = decl.typed_value.most_recent.typed_value; const mod_fn = tv.val.castTag(.function).?.data; - for (mod_fn.data.body.instructions) |inst| try genInst(buf, decl, inst); + for (mod_fn.body.instructions) |inst| try genInst(buf, decl, inst); // Write 'end' opcode try writer.writeByte(0x0B); diff --git a/src/llvm_backend.zig b/src/llvm_backend.zig index 5814aa7e7e..97406797b6 100644 --- a/src/llvm_backend.zig +++ b/src/llvm_backend.zig @@ -294,7 +294,7 @@ pub const LLVMIRModule = struct { const entry_block = llvm_func.appendBasicBlock("Entry"); self.builder.positionBuilderAtEnd(entry_block); - const instructions = func.data.body.instructions; + const instructions = func.body.instructions; for (instructions) |inst| { switch (inst.tag) { .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?), diff --git a/src/zir.zig b/src/zir.zig index 64b74f24d9..56ddee919c 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1864,13 +1864,15 @@ pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void { defer ctx.const_table.deinit(); defer ctx.arena.deinit(); - switch (module_fn.analysis()) { + switch (module_fn.state) { .queued => std.debug.print("(queued)", .{}), + .inline_only => std.debug.print("(inline_only)", .{}), .in_progress => std.debug.print("(in_progress)", .{}), .sema_failure => std.debug.print("(sema_failure)", .{}), .dependency_failure => std.debug.print("(dependency_failure)", .{}), - .success => |body| { - ctx.dump(body, std.io.getStdErr().writer()) catch @panic("failed to dump TZIR"); + .success => { + const writer = std.io.getStdErr().writer(); + ctx.dump(module_fn.body, writer) catch @panic("failed to dump TZIR"); }, } } @@ -2289,11 +2291,12 @@ const EmitZIR = struct { var instructions = std.ArrayList(*Inst).init(self.allocator); defer instructions.deinit(); - switch (module_fn.analysis()) { + switch (module_fn.state) { .queued => unreachable, .in_progress => unreachable, - .success => |body| { - try self.emitBody(body, &inst_table, &instructions); + .inline_only => unreachable, + .success => { + try self.emitBody(module_fn.body, &inst_table, &instructions); }, .sema_failure => { const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?; @@ -2372,7 +2375,7 @@ const EmitZIR = struct { .body = .{ .instructions = arena_instrs }, }, .kw_args = .{ - .is_inline = module_fn.bits.is_inline, + .is_inline = module_fn.state == .inline_only, }, }; return self.emitUnnamedDecl(&fn_inst.base); diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 9365996bb6..e8d995dd5e 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -25,8 +25,6 @@ const trace = @import("tracy.zig").trace; const Scope = Module.Scope; const InnerError = Module.InnerError; const Decl = Module.Decl; -const astgen = @import("astgen.zig"); -const ast = std.zig.ast; pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { switch (old_inst.tag) { @@ -861,7 +859,7 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError .function => func_val.castTag(.function).?.data, else => break :blk false, }; - break :blk module_fn.bits.is_inline; + break :blk module_fn.state == .inline_only; } break :blk false; }; @@ -874,76 +872,6 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError }), else => unreachable, }; - const callee_decl = module_fn.owner_decl; - // TODO: De-duplicate this with the code in Module.zig that generates - // ZIR for the same function and re-use the same ZIR for runtime function - // generation and for inline/comptime calls. - const callee_file_scope = callee_decl.getFileScope(); - const tree = mod.getAstTree(callee_file_scope) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => return error.AnalysisFail, - // TODO: make sure this gets retried and not cached - else => return mod.fail(scope, inst.base.src, "failed to load {s}: {s}", .{ - callee_file_scope.sub_file_path, @errorName(err), - }), - }; - const ast_node = tree.root_node.decls()[callee_decl.src_index]; - const fn_proto = ast_node.castTag(.FnProto).?; - - var call_arena = std.heap.ArenaAllocator.init(mod.gpa); - defer call_arena.deinit(); - - var gen_scope: Scope.GenZIR = .{ - .decl = callee_decl, - .arena = &call_arena.allocator, - .parent = callee_decl.scope, - }; - defer gen_scope.instructions.deinit(mod.gpa); - - // We need an instruction for each parameter, and they must be first in the body. - try gen_scope.instructions.resize(mod.gpa, fn_proto.params_len); - var params_scope = &gen_scope.base; - for (fn_proto.params()) |param, i| { - const name_token = param.name_token.?; - const src = tree.token_locs[name_token].start; - const param_name = try mod.identifierTokenString(scope, name_token); - const arg = try call_arena.allocator.create(zir.Inst.Arg); - arg.* = .{ - .base = .{ - .tag = .arg, - .src = src, - }, - .positionals = .{ - .name = param_name, - }, - .kw_args = .{}, - }; - gen_scope.instructions.items[i] = &arg.base; - const sub_scope = try call_arena.allocator.create(Scope.LocalVal); - sub_scope.* = .{ - .parent = params_scope, - .gen_zir = &gen_scope, - .name = param_name, - .inst = &arg.base, - }; - params_scope = &sub_scope.base; - } - - const body_node = fn_proto.getBodyNode().?; // We handle extern functions above. - const body_block = body_node.cast(ast.Node.Block).?; - - try astgen.blockExpr(mod, params_scope, body_block); - - if (gen_scope.instructions.items.len == 0 or - !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn()) - { - const src = tree.token_locs[body_block.rbrace].start; - _ = try astgen.addZIRNoOp(mod, &gen_scope.base, src, .returnvoid); - } - - if (mod.comp.verbose_ir) { - zir.dumpZir(mod.gpa, "fn_body_callee", callee_decl.name, gen_scope.instructions.items) catch {}; - } // Analyze the ZIR. The same ZIR gets analyzed into a runtime function // or an inlined call depending on what union tag the `label` field is @@ -986,9 +914,7 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError // This will have return instructions analyzed as break instructions to // the block_inst above. - try analyzeBody(mod, &child_block.base, .{ - .instructions = gen_scope.instructions.items, - }); + try analyzeBody(mod, &child_block.base, module_fn.zir); return analyzeBlockBody(mod, scope, &child_block, merges); } @@ -998,26 +924,11 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { const fn_type = try resolveType(mod, scope, fn_inst.positionals.fn_type); - const fn_zir = blk: { - var fn_arena = std.heap.ArenaAllocator.init(mod.gpa); - errdefer fn_arena.deinit(); - - const fn_zir = try scope.arena().create(Module.Fn.ZIR); - fn_zir.* = .{ - .body = .{ - .instructions = fn_inst.positionals.body.instructions, - }, - .arena = fn_arena.state, - }; - break :blk fn_zir; - }; const new_func = try scope.arena().create(Module.Fn); new_func.* = .{ - .bits = .{ - .state = .queued, - .is_inline = fn_inst.kw_args.is_inline, - }, - .data = .{ .zir = fn_zir }, + .state = if (fn_inst.kw_args.is_inline) .inline_only else .queued, + .zir = fn_inst.positionals.body, + .body = undefined, .owner_decl = scope.decl().?, }; return mod.constInst(scope, fn_inst.base.src, .{ diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 18c54b367e..79f5c3a73e 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -342,6 +342,7 @@ pub fn addCases(ctx: *TestContext) !void { , "", ); + // comptime function call case.addCompareOutput( \\export fn _start() noreturn { \\ exit(); @@ -365,6 +366,30 @@ pub fn addCases(ctx: *TestContext) !void { , "", ); + // Inline function call + case.addCompareOutput( + \\export fn _start() noreturn { + \\ var x: usize = 3; + \\ const y = add(1, 2, x); + \\ exit(y - 6); + \\} + \\ + \\inline fn add(a: usize, b: usize, c: usize) usize { + \\ return a + b + c; + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); } { -- cgit v1.2.3 From 7b8cede61fc20c137aca4e02425536bfc9a5a400 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 11:08:34 -0700 Subject: stage2: rework the C backend * std.ArrayList gains `moveToUnmanaged` and dead code `ArrayListUnmanaged.appendWrite` is deleted. * emit_h state is attached to Module rather than Compilation. * remove the implementation of emit-h because it did not properly integrate with incremental compilation. I will re-implement it in a follow-up commit. * Compilation: use the .codegen_failure tag rather than .dependency_failure tag for when `bin_file.updateDecl` fails. C backend: * Use a CValue tagged union instead of strings for C values. * Cleanly separate state into Object and DeclGen: - Object is present only when generating a .c file - DeclGen is present for both generating a .c and .h * Move some functions into their respective Object/DeclGen namespace. * Forward decls are managed by the incremental compilation frontend; C backend no longer renders function signatures based on callsites. For simplicity, all functions always get forward decls. * Constants are managed by the incremental compilation frontend. C backend no longer has a "constants" section. * Participate in incremental compilation. Each Decl gets an ArrayList for its generated C code and it is updated when the Decl is updated. During flush(), all these are joined together in the output file. * The new CValue tagged union is used to clean up using of assigning to locals without an additional pointer local. * Fix bug with bitcast of non-pointers making the memcpy destination immutable. --- lib/std/array_list.zig | 22 +- src/Compilation.zig | 58 +-- src/Module.zig | 6 +- src/codegen/c.zig | 950 +++++++++++++++++++++++++------------------------ src/link.zig | 19 +- src/link/C.zig | 195 ++++++---- src/link/C/zig.h | 45 +++ src/link/cbe.h | 44 --- src/test.zig | 17 +- test/stage2/cbe.zig | 2 - 10 files changed, 703 insertions(+), 655 deletions(-) create mode 100644 src/link/C/zig.h delete mode 100644 src/link/cbe.h (limited to 'src/codegen') diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 3114d1b744..51f5b8dc09 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -100,10 +100,20 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields /// of this ArrayList. This ArrayList retains ownership of underlying memory. + /// Deprecated: use `moveToUnmanaged` which has different semantics. pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) { return .{ .items = self.items, .capacity = self.capacity }; } + /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields + /// of this ArrayList. Empties this ArrayList. + pub fn moveToUnmanaged(self: *Self) ArrayListAlignedUnmanaged(T, alignment) { + const allocator = self.allocator; + const result = .{ .items = self.items, .capacity = self.capacity }; + self.* = init(allocator); + return result; + } + /// The caller owns the returned memory. Empties this ArrayList. pub fn toOwnedSlice(self: *Self) Slice { const allocator = self.allocator; @@ -551,14 +561,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ mem.copy(T, self.items[oldlen..], items); } - /// Same as `append` except it returns the number of bytes written, which is always the same - /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API. - /// This function may be called only when `T` is `u8`. - fn appendWrite(self: *Self, allocator: *Allocator, m: []const u8) !usize { - try self.appendSlice(allocator, m); - return m.len; - } - /// Append a value to the list `n` times. /// Allocates more memory as necessary. pub fn appendNTimes(self: *Self, allocator: *Allocator, value: T, n: usize) !void { @@ -1129,13 +1131,13 @@ test "std.ArrayList/ArrayListUnmanaged: ArrayList(T) of struct T" { } } -test "std.ArrayList(u8) implements outStream" { +test "std.ArrayList(u8) implements writer" { var buffer = ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); const x: i32 = 42; const y: i32 = 1234; - try buffer.outStream().print("x: {}\ny: {}\n", .{ x, y }); + try buffer.writer().print("x: {}\ny: {}\n", .{ x, y }); testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items); } diff --git a/src/Compilation.zig b/src/Compilation.zig index 9912520437..c654833270 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -138,8 +138,6 @@ emit_llvm_ir: ?EmitLoc, emit_analysis: ?EmitLoc, emit_docs: ?EmitLoc, -c_header: ?c_link.Header, - work_queue_wait_group: WaitGroup, pub const InnerError = Module.InnerError; @@ -866,9 +864,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .root_pkg = root_pkg, .root_scope = root_scope, .zig_cache_artifact_directory = zig_cache_artifact_directory, + .emit_h = options.emit_h, }; break :blk module; - } else null; + } else blk: { + if (options.emit_h != null) return error.NoZigModuleForCHeader; + break :blk null; + }; errdefer if (module) |zm| zm.deinit(); const error_return_tracing = !strip and switch (options.optimize_mode) { @@ -996,7 +998,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .local_cache_directory = options.local_cache_directory, .global_cache_directory = options.global_cache_directory, .bin_file = bin_file, - .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa, options.emit_h) else null, .emit_asm = options.emit_asm, .emit_llvm_ir = options.emit_llvm_ir, .emit_analysis = options.emit_analysis, @@ -1218,10 +1219,6 @@ pub fn destroy(self: *Compilation) void { } self.failed_c_objects.deinit(gpa); - if (self.c_header) |*header| { - header.deinit(); - } - self.cache_parent.manifest_dir.close(); if (self.owned_link_dir) |*dir| dir.close(); @@ -1325,20 +1322,6 @@ pub fn update(self: *Compilation) !void { module.root_scope.unload(self.gpa); } } - - // If we've chosen to emit a C header, flush the header to the disk. - if (self.c_header) |header| { - const header_path = header.emit_loc.?; - // If a directory has been provided, write the header there. Otherwise, just write it to the - // cache directory. - const header_dir = if (header_path.directory) |dir| - dir.handle - else - self.local_cache_directory.handle; - const header_file = try header_dir.createFile(header_path.basename, .{}); - defer header_file.close(); - try header.flush(header_file.writer()); - } } /// Having the file open for writing is problematic as far as executing the @@ -1497,7 +1480,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => { - decl.analysis = .dependency_failure; + decl.analysis = .codegen_failure; }, else => { try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); @@ -1512,25 +1495,6 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor } return; }; - - if (self.c_header) |*header| { - c_codegen.generateHeader(self, module, header, decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - decl.analysis = .dependency_failure; - }, - else => { - try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - module.gpa, - decl.src(), - "unable to generate C header: {s}", - .{@errorName(err)}, - )); - decl.analysis = .codegen_failure_retryable; - }, - }; - } }, }, .analyze_decl => |decl| { @@ -2998,9 +2962,9 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node man.hash.add(comp.bin_file.options.function_sections); man.hash.add(comp.bin_file.options.is_test); man.hash.add(comp.bin_file.options.emit != null); - man.hash.add(comp.c_header != null); - if (comp.c_header) |header| { - man.hash.addEmitLoc(header.emit_loc.?); + man.hash.add(mod.emit_h != null); + if (mod.emit_h) |emit_h| { + man.hash.addEmitLoc(emit_h); } man.hash.addOptionalEmitLoc(comp.emit_asm); man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); @@ -3105,10 +3069,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node }); break :blk try directory.join(arena, &[_][]const u8{bin_basename}); } else ""; - if (comp.c_header != null) { + if (comp.emit_h != null) { log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{}); } - const emit_h_path = try stage1LocPath(arena, if (comp.c_header) |header| header.emit_loc else null, directory); + const emit_h_path = try stage1LocPath(arena, mod.emit_h, directory); const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory); const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory); const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory); diff --git a/src/Module.zig b/src/Module.zig index 6a4575394a..f1cec82680 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -94,6 +94,8 @@ stage1_flags: packed struct { reserved: u2 = 0, } = .{}, +emit_h: ?Compilation.EmitLoc, + pub const Export = struct { options: std.builtin.ExportOptions, /// Byte offset into the file that contains the export directive. @@ -1943,14 +1945,14 @@ fn allocateNewDecl( .coff => .{ .coff = link.File.Coff.TextBlock.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, .macho => .{ .macho = link.File.MachO.TextBlock.empty }, - .c => .{ .c = {} }, + .c => .{ .c = link.File.C.DeclBlock.empty }, .wasm => .{ .wasm = {} }, }, .fn_link = switch (self.comp.bin_file.tag) { .coff => .{ .coff = {} }, .elf => .{ .elf = link.File.Elf.SrcFn.empty }, .macho => .{ .macho = link.File.MachO.SrcFn.empty }, - .c => .{ .c = {} }, + .c => .{ .c = link.File.C.FnBlock.empty }, .wasm => .{ .wasm = null }, }, .generation = 0, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1a89e22d48..d87801ae2e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1,495 +1,526 @@ const std = @import("std"); +const mem = std.mem; +const log = std.log.scoped(.c); +const Writer = std.ArrayList(u8).Writer; const link = @import("../link.zig"); const Module = @import("../Module.zig"); const Compilation = @import("../Compilation.zig"); - const Inst = @import("../ir.zig").Inst; const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; - const C = link.File.C; const Decl = Module.Decl; -const mem = std.mem; -const log = std.log.scoped(.c); +const trace = @import("../tracy.zig").trace; -const Writer = std.ArrayList(u8).Writer; +const Mutability = enum { Const, Mut }; -/// Maps a name from Zig source to C. Currently, this will always give the same -/// output for any given input, sometimes resulting in broken identifiers. -fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { - return allocator.dupe(u8, name); -} +pub const CValue = union(enum) { + none: void, + /// Index into local_names + local: usize, + /// Index into local_names, but take the address. + local_ref: usize, + /// A constant instruction, to be rendered inline. + constant: *Inst, + /// Index into the parameters + arg: usize, + /// By-value + decl: *Decl, -const Mutability = enum { Const, Mut }; + pub fn printed(value: CValue, object: *Object) Printed { + return .{ + .value = value, + .object = object, + }; + } + + pub const Printed = struct { + value: CValue, + object: *Object, + + /// TODO this got unwieldly, I want to remove the ability to print this way + pub fn format( + self: Printed, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) error{OutOfMemory}!void { + if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'"); + switch (self.value) { + .none => unreachable, + .local => |i| return std.fmt.format(writer, "t{d}", .{i}), + .local_ref => |i| return std.fmt.format(writer, "&t{d}", .{i}), + .constant => |inst| { + const o = self.object; + o.dg.renderValue(writer, inst.ty, inst.value().?) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => return, + }; + }, + .arg => |i| return std.fmt.format(writer, "a{d}", .{i}), + .decl => |decl| return writer.writeAll(mem.span(decl.name)), + } + } + }; +}; -fn renderTypeAndName( - ctx: *Context, - writer: Writer, - ty: Type, - name: []const u8, - mutability: Mutability, -) error{ OutOfMemory, AnalysisFail }!void { - var suffix = std.ArrayList(u8).init(&ctx.arena.allocator); - - var render_ty = ty; - while (render_ty.zigTypeTag() == .Array) { - const sentinel_bit = @boolToInt(render_ty.sentinel() != null); - const c_len = render_ty.arrayLen() + sentinel_bit; - try suffix.writer().print("[{d}]", .{c_len}); - render_ty = render_ty.elemType(); +pub const CValueMap = std.AutoHashMap(*Inst, CValue); + +/// This data is available when outputting .c code for a Module. +/// It is not available when generating .h file. +pub const Object = struct { + dg: DeclGen, + gpa: *mem.Allocator, + code: std.ArrayList(u8), + value_map: CValueMap, + next_arg_index: usize = 0, + next_local_index: usize = 0, + + fn resolveInst(o: *Object, inst: *Inst) !CValue { + if (inst.value()) |_| { + return CValue{ .constant = inst }; + } + return o.value_map.get(inst).?; // Instruction does not dominate all uses! } - try renderType(ctx, writer, render_ty); + fn allocLocalValue(o: *Object) CValue { + const result = o.next_local_index; + o.next_local_index += 1; + return .{ .local = result }; + } - const const_prefix = switch (mutability) { - .Const => "const ", - .Mut => "", - }; - try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items }); -} + fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue { + const local_value = o.allocLocalValue(); + try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability); + return local_value; + } -fn renderType( - ctx: *Context, - writer: Writer, - t: Type, -) error{ OutOfMemory, AnalysisFail }!void { - switch (t.zigTypeTag()) { - .NoReturn => { - try writer.writeAll("zig_noreturn void"); - }, - .Void => try writer.writeAll("void"), - .Bool => try writer.writeAll("bool"), - .Int => { - switch (t.tag()) { - .u8 => try writer.writeAll("uint8_t"), - .i8 => try writer.writeAll("int8_t"), - .u16 => try writer.writeAll("uint16_t"), - .i16 => try writer.writeAll("int16_t"), - .u32 => try writer.writeAll("uint32_t"), - .i32 => try writer.writeAll("int32_t"), - .u64 => try writer.writeAll("uint64_t"), - .i64 => try writer.writeAll("int64_t"), - .usize => try writer.writeAll("uintptr_t"), - .isize => try writer.writeAll("intptr_t"), - .c_short => try writer.writeAll("short"), - .c_ushort => try writer.writeAll("unsigned short"), - .c_int => try writer.writeAll("int"), - .c_uint => try writer.writeAll("unsigned int"), - .c_long => try writer.writeAll("long"), - .c_ulong => try writer.writeAll("unsigned long"), - .c_longlong => try writer.writeAll("long long"), - .c_ulonglong => try writer.writeAll("unsigned long long"), - .int_signed, .int_unsigned => { - const info = t.intInfo(ctx.target); - const sign_prefix = switch (info.signedness) { - .signed => "i", - .unsigned => "", - }; - inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { - if (info.bits <= nbits) { - try writer.print("{s}int{d}_t", .{ sign_prefix, nbits }); - break; - } + fn indent(o: *Object) !void { + const indent_size = 4; + const indent_level = 1; + const indent_amt = indent_size * indent_level; + try o.code.writer().writeByteNTimes(' ', indent_amt); + } + + fn renderTypeAndName( + o: *Object, + writer: Writer, + ty: Type, + name: CValue, + mutability: Mutability, + ) error{ OutOfMemory, AnalysisFail }!void { + var suffix = std.ArrayList(u8).init(o.gpa); + defer suffix.deinit(); + + var render_ty = ty; + while (render_ty.zigTypeTag() == .Array) { + const sentinel_bit = @boolToInt(render_ty.sentinel() != null); + const c_len = render_ty.arrayLen() + sentinel_bit; + try suffix.writer().print("[{d}]", .{c_len}); + render_ty = render_ty.elemType(); + } + + try o.dg.renderType(writer, render_ty); + + const const_prefix = switch (mutability) { + .Const => "const ", + .Mut => "", + }; + try writer.print(" {s}{}{s}", .{ const_prefix, name.printed(o), suffix.items }); + } +}; + +/// This data is available both when outputting .c code and when outputting an .h file. +const DeclGen = struct { + module: *Module, + decl: *Decl, + fwd_decl: std.ArrayList(u8), + error_msg: ?*Compilation.ErrorMsg, + + fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { + dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args); + return error.AnalysisFail; + } + + fn renderValue( + dg: *DeclGen, + writer: Writer, + t: Type, + val: Value, + ) error{ OutOfMemory, AnalysisFail }!void { + switch (t.zigTypeTag()) { + .Int => { + if (t.isSignedInt()) + return writer.print("{d}", .{val.toSignedInt()}); + return writer.print("{d}", .{val.toUnsignedInt()}); + }, + .Pointer => switch (val.tag()) { + .undef, .zero => try writer.writeAll("0"), + .one => try writer.writeAll("1"), + .decl_ref => { + const decl = val.castTag(.decl_ref).?.data; + + // Determine if we must pointer cast. + const decl_tv = decl.typed_value.most_recent.typed_value; + if (t.eql(decl_tv.ty)) { + try writer.print("&{s}", .{decl.name}); } else { - return ctx.fail(ctx.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); + try writer.writeAll("("); + try dg.renderType(writer, t); + try writer.print(")&{s}", .{decl.name}); } }, + .function => { + const func = val.castTag(.function).?.data; + try writer.print("{s}", .{func.owner_decl.name}); + }, + .extern_fn => { + const decl = val.castTag(.extern_fn).?.data; + try writer.print("{s}", .{decl.name}); + }, + else => |e| return dg.fail( + dg.decl.src(), + "TODO: C backend: implement Pointer value {s}", + .{@tagName(e)}, + ), + }, + .Array => { + // 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 {Z} + try writer.print("\"{Z}\"", .{bytes}); + }, + else => { + // Fall back to generic implementation. + var arena = std.heap.ArenaAllocator.init(dg.module.gpa); + defer arena.deinit(); + + try writer.writeAll("{"); + var index: usize = 0; + const len = t.arrayLen(); + const elem_ty = t.elemType(); + while (index < len) : (index += 1) { + if (index != 0) try writer.writeAll(","); + const elem_val = try val.elemValue(&arena.allocator, index); + try dg.renderValue(writer, elem_ty, elem_val); + } + if (t.sentinel()) |sentinel_val| { + if (index != 0) try writer.writeAll(","); + try dg.renderValue(writer, elem_ty, sentinel_val); + } + try writer.writeAll("}"); + }, + } + }, + else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ + @tagName(e), + }), + } + } + + fn renderFunctionSignature(dg: *DeclGen, w: Writer) !void { + const tv = dg.decl.typed_value.most_recent.typed_value; + // Determine whether the function is globally visible. + const is_global = blk: { + switch (tv.val.tag()) { + .extern_fn => break :blk true, + .function => { + const func = tv.val.castTag(.function).?.data; + break :blk dg.module.decl_exports.contains(func.owner_decl); + }, else => unreachable, } - }, - .Pointer => { - if (t.isSlice()) { - return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{}); - } else { - try renderType(ctx, writer, t.elemType()); - try writer.writeAll(" *"); - if (t.isConstPtr()) { - try writer.writeAll("const "); - } - if (t.isVolatilePtr()) { - try writer.writeAll("volatile "); + }; + if (!is_global) { + try w.writeAll("static "); + } + try dg.renderType(w, tv.ty.fnReturnType()); + const decl_name = mem.span(dg.decl.name); + try w.print(" {s}(", .{decl_name}); + var param_len = tv.ty.fnParamLen(); + if (param_len == 0) + try w.writeAll("void") + else { + var index: usize = 0; + while (index < param_len) : (index += 1) { + if (index > 0) { + try w.writeAll(", "); } + try dg.renderType(w, tv.ty.fnParamType(index)); + try w.print(" a{d}", .{index}); } - }, - .Array => { - try renderType(ctx, writer, t.elemType()); - try writer.writeAll(" *"); - }, - else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement type {s}", .{ - @tagName(e), - }), + } + try w.writeByte(')'); } -} -fn renderValue( - ctx: *Context, - writer: Writer, - t: Type, - val: Value, -) error{ OutOfMemory, AnalysisFail }!void { - switch (t.zigTypeTag()) { - .Int => { - if (t.isSignedInt()) - return writer.print("{d}", .{val.toSignedInt()}); - return writer.print("{d}", .{val.toUnsignedInt()}); - }, - .Pointer => switch (val.tag()) { - .undef, .zero => try writer.writeAll("0"), - .one => try writer.writeAll("1"), - .decl_ref => { - const decl = val.castTag(.decl_ref).?.data; - - // Determine if we must pointer cast. - const decl_tv = decl.typed_value.most_recent.typed_value; - if (t.eql(decl_tv.ty)) { - try writer.print("&{s}", .{decl.name}); - } else { - try writer.writeAll("("); - try renderType(ctx, writer, t); - try writer.print(")&{s}", .{decl.name}); - } + fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void { + switch (t.zigTypeTag()) { + .NoReturn => { + try w.writeAll("zig_noreturn void"); }, - .function => { - const func = val.castTag(.function).?.data; - try writer.print("{s}", .{func.owner_decl.name}); - }, - .extern_fn => { - const decl = val.castTag(.extern_fn).?.data; - try writer.print("{s}", .{decl.name}); + .Void => try w.writeAll("void"), + .Bool => try w.writeAll("bool"), + .Int => { + switch (t.tag()) { + .u8 => try w.writeAll("uint8_t"), + .i8 => try w.writeAll("int8_t"), + .u16 => try w.writeAll("uint16_t"), + .i16 => try w.writeAll("int16_t"), + .u32 => try w.writeAll("uint32_t"), + .i32 => try w.writeAll("int32_t"), + .u64 => try w.writeAll("uint64_t"), + .i64 => try w.writeAll("int64_t"), + .usize => try w.writeAll("uintptr_t"), + .isize => try w.writeAll("intptr_t"), + .c_short => try w.writeAll("short"), + .c_ushort => try w.writeAll("unsigned short"), + .c_int => try w.writeAll("int"), + .c_uint => try w.writeAll("unsigned int"), + .c_long => try w.writeAll("long"), + .c_ulong => try w.writeAll("unsigned long"), + .c_longlong => try w.writeAll("long long"), + .c_ulonglong => try w.writeAll("unsigned long long"), + .int_signed, .int_unsigned => { + const info = t.intInfo(dg.module.getTarget()); + const sign_prefix = switch (info.signedness) { + .signed => "i", + .unsigned => "", + }; + inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { + if (info.bits <= nbits) { + try w.print("{s}int{d}_t", .{ sign_prefix, nbits }); + break; + } + } else { + return dg.fail(dg.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); + } + }, + else => unreachable, + } }, - else => |e| return ctx.fail( - ctx.decl.src(), - "TODO: C backend: implement Pointer value {s}", - .{@tagName(e)}, - ), - }, - .Array => { - // 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 {Z} - try writer.print("\"{Z}\"", .{bytes}); - }, - else => { - // Fall back to generic implementation. - try writer.writeAll("{"); - var index: usize = 0; - const len = t.arrayLen(); - const elem_ty = t.elemType(); - while (index < len) : (index += 1) { - if (index != 0) try writer.writeAll(","); - const elem_val = try val.elemValue(&ctx.arena.allocator, index); - try renderValue(ctx, writer, elem_ty, elem_val); + .Pointer => { + if (t.isSlice()) { + return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{}); + } else { + try dg.renderType(w, t.elemType()); + try w.writeAll(" *"); + if (t.isConstPtr()) { + try w.writeAll("const "); } - if (t.sentinel()) |sentinel_val| { - if (index != 0) try writer.writeAll(","); - try renderValue(ctx, writer, elem_ty, sentinel_val); + if (t.isVolatilePtr()) { + try w.writeAll("volatile "); } - try writer.writeAll("}"); - }, - } - }, - else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{ - @tagName(e), - }), - } -} - -fn renderFunctionSignature( - ctx: *Context, - writer: Writer, - decl: *Decl, -) !void { - const tv = decl.typed_value.most_recent.typed_value; - // Determine whether the function is globally visible. - const is_global = blk: { - switch (tv.val.tag()) { - .extern_fn => break :blk true, - .function => { - const func = tv.val.castTag(.function).?.data; - break :blk ctx.module.decl_exports.contains(func.owner_decl); + } }, - else => unreachable, - } - }; - if (!is_global) { - try writer.writeAll("static "); - } - try renderType(ctx, writer, tv.ty.fnReturnType()); - // Use the child allocator directly, as we know the name can be freed before - // the rest of the arena. - const decl_name = mem.span(decl.name); - const name = try map(ctx.arena.child_allocator, decl_name); - defer ctx.arena.child_allocator.free(name); - try writer.print(" {s}(", .{name}); - var param_len = tv.ty.fnParamLen(); - if (param_len == 0) - try writer.writeAll("void") - else { - var index: usize = 0; - while (index < param_len) : (index += 1) { - if (index > 0) { - try writer.writeAll(", "); - } - try renderType(ctx, writer, tv.ty.fnParamType(index)); - try writer.print(" arg{d}", .{index}); + .Array => { + try dg.renderType(w, t.elemType()); + try w.writeAll(" *"); + }, + else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ + @tagName(e), + }), } } - try writer.writeByte(')'); -} +}; -fn indent(file: *C) !void { - const indent_size = 4; - const indent_level = 1; - const indent_amt = indent_size * indent_level; - try file.main.writer().writeByteNTimes(' ', indent_amt); -} +pub fn genDecl(o: *Object) !void { + const tracy = trace(@src()); + defer tracy.end(); -pub fn generate(file: *C, module: *Module, decl: *Decl) !void { - const tv = decl.typed_value.most_recent.typed_value; - - var arena = std.heap.ArenaAllocator.init(file.base.allocator); - defer arena.deinit(); - var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); - defer inst_map.deinit(); - var ctx = Context{ - .decl = decl, - .arena = &arena, - .inst_map = &inst_map, - .target = file.base.options.target, - .header = &file.header, - .module = module, - }; - defer { - file.error_msg = ctx.error_msg; - ctx.deinit(); - } + const tv = o.dg.decl.typed_value.most_recent.typed_value; if (tv.val.castTag(.function)) |func_payload| { - const writer = file.main.writer(); - try renderFunctionSignature(&ctx, writer, decl); - - try writer.writeAll(" {"); + const fwd_decl_writer = o.dg.fwd_decl.writer(); + try o.dg.renderFunctionSignature(fwd_decl_writer); + try fwd_decl_writer.writeAll(";\n"); const func: *Module.Fn = func_payload.data; const instructions = func.body.instructions; - if (instructions.len > 0) { - try writer.writeAll("\n"); - for (instructions) |inst| { - if (switch (inst.tag) { - .add => try genBinOp(&ctx, file, inst.castTag(.add).?, "+"), - .alloc => try genAlloc(&ctx, file, inst.castTag(.alloc).?), - .arg => try genArg(&ctx), - .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), - .block => try genBlock(&ctx, file, inst.castTag(.block).?), - .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?), - .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), - .call => try genCall(&ctx, file, inst.castTag(.call).?), - .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="), - .cmp_gt => try genBinOp(&ctx, file, inst.castTag(.cmp_gt).?, ">"), - .cmp_gte => try genBinOp(&ctx, file, inst.castTag(.cmp_gte).?, ">="), - .cmp_lt => try genBinOp(&ctx, file, inst.castTag(.cmp_lt).?, "<"), - .cmp_lte => try genBinOp(&ctx, file, inst.castTag(.cmp_lte).?, "<="), - .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="), - .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), - .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), - .load => try genLoad(&ctx, file, inst.castTag(.load).?), - .ret => try genRet(&ctx, file, inst.castTag(.ret).?), - .retvoid => try genRetVoid(file), - .store => try genStore(&ctx, file, inst.castTag(.store).?), - .sub => try genBinOp(&ctx, file, inst.castTag(.sub).?, "-"), - .unreach => try genUnreach(file, inst.castTag(.unreach).?), - else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}), - }) |name| { - try ctx.inst_map.putNoClobber(inst, name); - } + const writer = o.code.writer(); + try o.dg.renderFunctionSignature(writer); + if (instructions.len == 0) { + try writer.writeAll(" {}\n\n"); + return; + } + + try writer.writeAll(" {"); + + try writer.writeAll("\n"); + for (instructions) |inst| { + const result_value = switch (inst.tag) { + .add => try genBinOp(o, inst.castTag(.add).?, "+"), + .alloc => try genAlloc(o, inst.castTag(.alloc).?), + .arg => genArg(o), + .assembly => try genAsm(o, inst.castTag(.assembly).?), + .block => try genBlock(o, inst.castTag(.block).?), + .bitcast => try genBitcast(o, inst.castTag(.bitcast).?), + .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?), + .call => try genCall(o, inst.castTag(.call).?), + .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, "=="), + .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, ">"), + .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, ">="), + .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, "<"), + .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, "<="), + .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, "!="), + .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?), + .intcast => try genIntCast(o, inst.castTag(.intcast).?), + .load => try genLoad(o, inst.castTag(.load).?), + .ret => try genRet(o, inst.castTag(.ret).?), + .retvoid => try genRetVoid(o), + .store => try genStore(o, inst.castTag(.store).?), + .sub => try genBinOp(o, inst.castTag(.sub).?, "-"), + .unreach => try genUnreach(o, inst.castTag(.unreach).?), + else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), + }; + switch (result_value) { + .none => {}, + else => try o.value_map.putNoClobber(inst, result_value), } } try writer.writeAll("}\n\n"); } else if (tv.val.tag() == .extern_fn) { - return; // handled when referenced + const writer = o.code.writer(); + try o.dg.renderFunctionSignature(writer); + try writer.writeAll(";\n"); } else { - const writer = file.constants.writer(); + const writer = o.code.writer(); try writer.writeAll("static "); // TODO ask the Decl if it is const // https://github.com/ziglang/zig/issues/7582 - try renderTypeAndName(&ctx, writer, tv.ty, mem.span(decl.name), .Mut); + const decl_c_value: CValue = .{ .decl = o.dg.decl }; + try o.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut); try writer.writeAll(" = "); - try renderValue(&ctx, writer, tv.ty, tv.val); + try o.dg.renderValue(writer, tv.ty, tv.val); try writer.writeAll(";\n"); } } -pub fn generateHeader( - comp: *Compilation, - module: *Module, - header: *C.Header, - decl: *Decl, -) error{ AnalysisFail, OutOfMemory }!void { +pub fn genHeader(comp: *Compilation, dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { + const tracy = trace(@src()); + defer tracy.end(); + switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { .Fn => { - var inst_map = std.AutoHashMap(*Inst, []u8).init(comp.gpa); - defer inst_map.deinit(); - - var arena = std.heap.ArenaAllocator.init(comp.gpa); - defer arena.deinit(); - - var ctx = Context{ - .decl = decl, - .arena = &arena, - .inst_map = &inst_map, - .target = comp.getTarget(), - .header = header, - .module = module, - }; - const writer = header.buf.writer(); - renderFunctionSignature(&ctx, writer, decl) catch |err| { - if (err == error.AnalysisFail) { - try module.failed_decls.put(module.gpa, decl, ctx.error_msg); - } - return err; + dg.renderFunctionSignature() catch |err| switch (err) { + error.AnalysisFail => { + try dg.module.failed_decls.put(dg.module.gpa, decl, dg.error_msg.?); + dg.error_msg = null; + return error.AnalysisFail; + }, + else => |e| return e, }; - try writer.writeAll(";\n"); + try dg.fwd_decl.appendSlice(";\n"); }, else => {}, } } -const Context = struct { - decl: *Decl, - inst_map: *std.AutoHashMap(*Inst, []u8), - arena: *std.heap.ArenaAllocator, - argdex: usize = 0, - unnamed_index: usize = 0, - error_msg: *Compilation.ErrorMsg = undefined, - target: std.Target, - header: *C.Header, - module: *Module, - - fn resolveInst(self: *Context, inst: *Inst) ![]u8 { - if (inst.value()) |val| { - var out = std.ArrayList(u8).init(&self.arena.allocator); - try renderValue(self, out.writer(), inst.ty, val); - return out.toOwnedSlice(); - } - return self.inst_map.get(inst).?; // Instruction does not dominate all uses! - } - - fn name(self: *Context) ![]u8 { - const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{d}", .{self.unnamed_index}); - self.unnamed_index += 1; - return val; - } - - fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { - self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args); - return error.AnalysisFail; - } - - fn deinit(self: *Context) void { - self.* = undefined; - } -}; - -fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 { - const writer = file.main.writer(); +fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue { + const writer = o.code.writer(); // First line: the variable used as data storage. - try indent(file); - const local_name = try ctx.name(); + try o.indent(); const elem_type = alloc.base.ty.elemType(); const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut; - try renderTypeAndName(ctx, writer, elem_type, local_name, mutability); + const local = try o.allocLocal(elem_type, mutability); try writer.writeAll(";\n"); - // Second line: a pointer to it so that we can refer to it as the allocation. - // One line for the variable, one line for the pointer to the variable, which we return. - try indent(file); - const ptr_local_name = try ctx.name(); - try renderTypeAndName(ctx, writer, alloc.base.ty, ptr_local_name, .Const); - try writer.print(" = &{s};\n", .{local_name}); - - return ptr_local_name; + return CValue{ .local_ref = local.local }; } -fn genArg(ctx: *Context) !?[]u8 { - const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{d}", .{ctx.argdex}); - ctx.argdex += 1; - return name; +fn genArg(o: *Object) CValue { + const i = o.next_arg_index; + o.next_arg_index += 1; + return .{ .arg = i }; } -fn genRetVoid(file: *C) !?[]u8 { - try indent(file); - try file.main.writer().print("return;\n", .{}); - return null; +fn genRetVoid(o: *Object) !CValue { + try o.indent(); + try o.code.writer().print("return;\n", .{}); + return CValue.none; } -fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { - const operand = try ctx.resolveInst(inst.operand); - const writer = file.main.writer(); - try indent(file); - const local_name = try ctx.name(); - try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); - try writer.print(" = *{s};\n", .{operand}); - return local_name; +fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { + const operand = try o.resolveInst(inst.operand); + const writer = o.code.writer(); + try o.indent(); + const local = try o.allocLocal(inst.base.ty, .Const); + switch (operand) { + .local_ref => |i| { + const wrapped: CValue = .{ .local = i }; + try writer.print(" = {};\n", .{wrapped.printed(o)}); + }, + else => { + try writer.print(" = *{};\n", .{operand.printed(o)}); + }, + } + return local; } -fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { - try indent(file); - const writer = file.main.writer(); - try writer.print("return {s};\n", .{try ctx.resolveInst(inst.operand)}); - return null; +fn genRet(o: *Object, inst: *Inst.UnOp) !CValue { + const operand = try o.resolveInst(inst.operand); + try o.indent(); + try o.code.writer().print("return {};\n", .{operand.printed(o)}); + return CValue.none; } -fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { +fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue { if (inst.base.isUnused()) - return null; - try indent(file); - const writer = file.main.writer(); - const name = try ctx.name(); - const from = try ctx.resolveInst(inst.operand); + return CValue.none; - try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); + const from = try o.resolveInst(inst.operand); + + try o.indent(); + const writer = o.code.writer(); + const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); - try renderType(ctx, writer, inst.base.ty); - try writer.print("){s};\n", .{from}); - return name; + try o.dg.renderType(writer, inst.base.ty); + try writer.print("){};\n", .{from.printed(o)}); + return local; } -fn genStore(ctx: *Context, file: *C, inst: *Inst.BinOp) !?[]u8 { +fn genStore(o: *Object, inst: *Inst.BinOp) !CValue { // *a = b; - try indent(file); - const writer = file.main.writer(); - const dest_ptr_name = try ctx.resolveInst(inst.lhs); - const src_val_name = try ctx.resolveInst(inst.rhs); - try writer.print("*{s} = {s};\n", .{ dest_ptr_name, src_val_name }); - return null; + const dest_ptr = try o.resolveInst(inst.lhs); + const src_val = try o.resolveInst(inst.rhs); + + try o.indent(); + const writer = o.code.writer(); + switch (dest_ptr) { + .local_ref => |i| { + const dest: CValue = .{ .local = i }; + try writer.print("{} = {};\n", .{ dest.printed(o), src_val.printed(o) }); + }, + else => { + try writer.print("*{} = {};\n", .{ dest_ptr.printed(o), src_val.printed(o) }); + }, + } + return CValue.none; } -fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !?[]u8 { +fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { if (inst.base.isUnused()) - return null; - try indent(file); - const lhs = try ctx.resolveInst(inst.lhs); - const rhs = try ctx.resolveInst(inst.rhs); - const writer = file.main.writer(); - const name = try ctx.name(); - try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); - try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs }); - return name; + return CValue.none; + + const lhs = try o.resolveInst(inst.lhs); + const rhs = try o.resolveInst(inst.rhs); + + try o.indent(); + const writer = o.code.writer(); + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.print(" = {} {s} {};\n", .{ lhs.printed(o), operator, rhs.printed(o) }); + return local; } -fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { - try indent(file); - const writer = file.main.writer(); - const header = file.header.buf.writer(); +fn genCall(o: *Object, inst: *Inst.Call) !CValue { if (inst.func.castTag(.constant)) |func_inst| { const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn| extern_fn.data @@ -501,23 +532,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty; const ret_ty = fn_ty.fnReturnType(); const unused_result = inst.base.isUnused(); - var result_name: ?[]u8 = null; + var result_local: CValue = .none; + + try o.indent(); + const writer = o.code.writer(); if (unused_result) { if (ret_ty.hasCodeGenBits()) { try writer.print("(void)", .{}); } } else { - const local_name = try ctx.name(); - try renderTypeAndName(ctx, writer, ret_ty, local_name, .Const); + result_local = try o.allocLocal(ret_ty, .Const); try writer.writeAll(" = "); - result_name = local_name; } const fn_name = mem.spanZ(fn_decl.name); - if (file.called.get(fn_name) == null) { - try file.called.put(fn_name, {}); - try renderFunctionSignature(ctx, header, fn_decl); - try header.writeAll(";\n"); - } try writer.print("{s}(", .{fn_name}); if (inst.args.len != 0) { for (inst.args) |arg, i| { @@ -525,87 +552,88 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { try writer.writeAll(", "); } if (arg.value()) |val| { - try renderValue(ctx, writer, arg.ty, val); + try o.dg.renderValue(writer, arg.ty, val); } else { - const val = try ctx.resolveInst(arg); - try writer.print("{s}", .{val}); + const val = try o.resolveInst(arg); + try writer.print("{}", .{val.printed(o)}); } } } try writer.writeAll(");\n"); - return result_name; + return result_local; } else { - return ctx.fail(ctx.decl.src(), "TODO: C backend: implement function pointers", .{}); + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{}); } } -fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { +fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue { // TODO emit #line directive here with line number and filename - return null; + return CValue.none; } -fn genBlock(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 { - return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{}); +fn genBlock(o: *Object, inst: *Inst.Block) !CValue { + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{}); } -fn genBitcast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { - const writer = file.main.writer(); - try indent(file); - const local_name = try ctx.name(); - const operand = try ctx.resolveInst(inst.operand); - try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); +fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { + const operand = try o.resolveInst(inst.operand); + + const writer = o.code.writer(); + try o.indent(); if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { + const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); - try renderType(ctx, writer, inst.base.ty); - try writer.print("){s};\n", .{operand}); - } else { - try writer.writeAll(";\n"); - try indent(file); - try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name }); + try o.dg.renderType(writer, inst.base.ty); + try writer.print("){};\n", .{operand.printed(o)}); + return local; } - return local_name; + + const local = try o.allocLocal(inst.base.ty, .Mut); + try writer.writeAll(";\n"); + try o.indent(); + try writer.print("memcpy(&{}, &{}, sizeof {});\n", .{ + local.printed(o), operand.printed(o), local.printed(o), + }); + return local; } -fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 { - try indent(file); - try file.main.writer().writeAll("zig_breakpoint();\n"); - return null; +fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue { + try o.indent(); + try o.code.writer().writeAll("zig_breakpoint();\n"); + return CValue.none; } -fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { - try indent(file); - try file.main.writer().writeAll("zig_unreachable();\n"); - return null; +fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { + try o.indent(); + try o.code.writer().writeAll("zig_unreachable();\n"); + return CValue.none; } -fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { - try indent(file); - const writer = file.main.writer(); +fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { + if (as.base.isUnused() and !as.is_volatile) + return CValue.none; + + const writer = o.code.writer(); for (as.inputs) |i, index| { if (i[0] == '{' and i[i.len - 1] == '}') { const reg = i[1 .. i.len - 1]; const arg = as.args[index]; + const arg_c_value = try o.resolveInst(arg); + try o.indent(); try writer.writeAll("register "); - try renderType(ctx, writer, arg.ty); - try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); - // TODO merge constant handling into inst_map as well - if (arg.castTag(.constant)) |c| { - try renderValue(ctx, writer, arg.ty, c.val); - try writer.writeAll(";\n "); - } else { - const gop = try ctx.inst_map.getOrPut(arg); - if (!gop.found_existing) { - return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); - } - try writer.print("{s};\n ", .{gop.entry.value}); - } + try o.dg.renderType(writer, arg.ty); + try writer.print(" {s}_constant __asm__(\"{s}\") = {};\n", .{ + reg, reg, arg_c_value.printed(o), + }); } else { - return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); + return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{}); } } - try writer.print("__asm {s} (\"{s}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); - if (as.output) |o| { - return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{}); + try o.indent(); + const volatile_string: []const u8 = if (as.is_volatile) "volatile " else ""; + try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source }); + if (as.output) |_| { + return o.dg.fail(o.dg.decl.src(), "TODO inline asm output", .{}); } if (as.inputs.len > 0) { if (as.output == null) { @@ -627,5 +655,9 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { } } try writer.writeAll(");\n"); - return null; + + if (as.base.isUnused()) + return CValue.none; + + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{}); } diff --git a/src/link.zig b/src/link.zig index 488f8bf69b..18b093a07a 100644 --- a/src/link.zig +++ b/src/link.zig @@ -130,7 +130,7 @@ pub const File = struct { elf: Elf.TextBlock, coff: Coff.TextBlock, macho: MachO.TextBlock, - c: void, + c: C.DeclBlock, wasm: void, }; @@ -138,7 +138,7 @@ pub const File = struct { elf: Elf.SrcFn, coff: Coff.SrcFn, macho: MachO.SrcFn, - c: void, + c: C.FnBlock, wasm: ?Wasm.FnData, }; @@ -291,7 +291,7 @@ pub const File = struct { .coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl), .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl), - .c => {}, + .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl), } } @@ -301,7 +301,8 @@ pub const File = struct { .coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl), .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl), - .c, .wasm => {}, + .c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl), + .wasm => {}, } } @@ -312,7 +313,8 @@ pub const File = struct { .coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl), .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl), - .c, .wasm => {}, + .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl), + .wasm => {}, } } @@ -407,12 +409,13 @@ pub const File = struct { } } + /// Called when a Decl is deleted from the Module. pub fn freeDecl(base: *File, decl: *Module.Decl) void { switch (base.tag) { .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl), .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), - .c => {}, + .c => @fieldParentPtr(C, "base", base).freeDecl(decl), .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl), } } @@ -432,14 +435,14 @@ pub const File = struct { pub fn updateDeclExports( base: *File, module: *Module, - decl: *const Module.Decl, + decl: *Module.Decl, exports: []const *Module.Export, ) !void { switch (base.tag) { .coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports), .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports), - .c => return {}, + .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports), .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports), } } diff --git a/src/link/C.zig b/src/link/C.zig index 5f38c9324f..10b98b854c 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -11,45 +11,28 @@ const trace = @import("../tracy.zig").trace; const C = @This(); pub const base_tag: link.File.Tag = .c; +pub const zig_h = @embedFile("C/zig.h"); -pub const Header = struct { - buf: std.ArrayList(u8), - emit_loc: ?Compilation.EmitLoc, - - pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header { - return .{ - .buf = std.ArrayList(u8).init(allocator), - .emit_loc = emit_loc, - }; - } - - pub fn flush(self: *const Header, writer: anytype) !void { - const tracy = trace(@src()); - defer tracy.end(); +base: link.File, - try writer.writeAll(@embedFile("cbe.h")); - if (self.buf.items.len > 0) { - try writer.print("{s}", .{self.buf.items}); - } - } +/// Per-declaration data. For functions this is the body, and +/// the forward declaration is stored in the FnBlock. +pub const DeclBlock = struct { + code: std.ArrayListUnmanaged(u8), - pub fn deinit(self: *Header) void { - self.buf.deinit(); - self.* = undefined; - } + pub const empty: DeclBlock = .{ + .code = .{}, + }; }; -base: link.File, - -path: []const u8, +/// Per-function data. +pub const FnBlock = struct { + fwd_decl: std.ArrayListUnmanaged(u8), -// These are only valid during a flush()! -header: Header, -constants: std.ArrayList(u8), -main: std.ArrayList(u8), -called: std.StringHashMap(void), - -error_msg: *Compilation.ErrorMsg = undefined, + pub const empty: FnBlock = .{ + .fwd_decl = .{}, + }; +}; pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { assert(options.object_format == .c); @@ -57,6 +40,14 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_llvm) return error.LLVMHasNoCBackend; if (options.use_lld) return error.LLDHasNoCBackend; + const file = try options.emit.?.directory.handle.createFile(sub_path, .{ + .truncate = true, + .mode = link.determineMode(options), + }); + errdefer file.close(); + + try file.writeAll(zig_h); + var c_file = try allocator.create(C); errdefer allocator.destroy(c_file); @@ -64,25 +55,75 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio .base = .{ .tag = .c, .options = options, - .file = null, + .file = file, .allocator = allocator, }, - .main = undefined, - .header = undefined, - .constants = undefined, - .called = undefined, - .path = sub_path, }; return c_file; } -pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { - self.error_msg = try Compilation.ErrorMsg.create(self.base.allocator, src, format, args); - return error.AnalysisFail; +pub fn deinit(self: *C) void { + const module = self.base.options.module orelse return; + for (module.decl_table.items()) |entry| { + self.freeDecl(entry.value); + } +} + +pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {} + +pub fn freeDecl(self: *C, decl: *Module.Decl) void { + decl.link.c.code.deinit(self.base.allocator); + decl.fn_link.c.fwd_decl.deinit(self.base.allocator); +} + +pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const fwd_decl = &decl.fn_link.c.fwd_decl; + const code = &decl.link.c.code; + fwd_decl.shrinkRetainingCapacity(0); + code.shrinkRetainingCapacity(0); + + var object: codegen.Object = .{ + .dg = .{ + .module = module, + .error_msg = null, + .decl = decl, + .fwd_decl = fwd_decl.toManaged(module.gpa), + }, + .gpa = module.gpa, + .code = code.toManaged(module.gpa), + .value_map = codegen.CValueMap.init(module.gpa), + }; + defer object.value_map.deinit(); + defer object.code.deinit(); + defer object.dg.fwd_decl.deinit(); + + codegen.genDecl(&object) catch |err| switch (err) { + error.AnalysisFail => {}, + else => |e| return e, + }; + // The code may populate this error without returning error.AnalysisFail. + if (object.dg.error_msg) |msg| { + try module.failed_decls.put(module.gpa, decl, msg); + return; + } + + fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); + code.* = object.code.moveToUnmanaged(); + + // Free excess allocated memory for this Decl. + fwd_decl.shrink(module.gpa, fwd_decl.items.len); + code.shrink(module.gpa, code.items.len); } -pub fn deinit(self: *C) void {} +pub fn updateDeclLineNumber(self: *C, module: *Module, decl: *Module.Decl) !void { + // The C backend does not have the ability to fix line numbers without re-generating + // the entire Decl. + return self.updateDecl(module, decl); +} pub fn flush(self: *C, comp: *Compilation) !void { return self.flushModule(comp); @@ -92,41 +133,45 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - self.main = std.ArrayList(u8).init(self.base.allocator); - self.header = Header.init(self.base.allocator, null); - self.constants = std.ArrayList(u8).init(self.base.allocator); - self.called = std.StringHashMap(void).init(self.base.allocator); - defer self.main.deinit(); - defer self.header.deinit(); - defer self.constants.deinit(); - defer self.called.deinit(); - - const module = self.base.options.module.?; - for (self.base.options.module.?.decl_table.entries.items) |kv| { - codegen.generate(self, module, kv.value) catch |err| { - if (err == error.AnalysisFail) { - try module.failed_decls.put(module.gpa, kv.value, self.error_msg); - } - return err; - }; - } + const file = self.base.file.?; - const file = try self.base.options.emit.?.directory.handle.createFile(self.path, .{ .truncate = true, .read = true, .mode = link.determineMode(self.base.options) }); - defer file.close(); + // The header is written upon opening; here we truncate and seek to after the header. + // TODO: use writev + try file.seekTo(zig_h.len); + try file.setEndPos(zig_h.len); - const writer = file.writer(); - try self.header.flush(writer); - if (self.header.buf.items.len > 0) { - try writer.writeByte('\n'); - } - if (self.constants.items.len > 0) { - try writer.print("{s}\n", .{self.constants.items}); + var buffered_writer = std.io.bufferedWriter(file.writer()); + const writer = buffered_writer.writer(); + + const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; + + // Forward decls and non-functions first. + // TODO: use writev + for (module.decl_table.items()) |kv| { + const decl = kv.value; + const decl_tv = decl.typed_value.most_recent.typed_value; + if (decl_tv.val.castTag(.function)) |_| { + try writer.writeAll(decl.fn_link.c.fwd_decl.items); + } else { + try writer.writeAll(decl.link.c.code.items); + } } - if (self.main.items.len > 1) { - const last_two = self.main.items[self.main.items.len - 2 ..]; - if (std.mem.eql(u8, last_two, "\n\n")) { - self.main.items.len -= 1; + + // Now the function bodies. + for (module.decl_table.items()) |kv| { + const decl = kv.value; + const decl_tv = decl.typed_value.most_recent.typed_value; + if (decl_tv.val.castTag(.function)) |_| { + try writer.writeAll(decl.link.c.code.items); } } - try writer.writeAll(self.main.items); + + try buffered_writer.flush(); } + +pub fn updateDeclExports( + self: *C, + module: *Module, + decl: *Module.Decl, + exports: []const *Module.Export, +) !void {} diff --git a/src/link/C/zig.h b/src/link/C/zig.h new file mode 100644 index 0000000000..49f97210eb --- /dev/null +++ b/src/link/C/zig.h @@ -0,0 +1,45 @@ +#if __STDC_VERSION__ >= 199901L +#include +#else +#define bool unsigned char +#define true 1 +#define false 0 +#endif + +#if __STDC_VERSION__ >= 201112L +#define zig_noreturn _Noreturn +#elif __GNUC__ +#define zig_noreturn __attribute__ ((noreturn)) +#elif _MSC_VER +#define zig_noreturn __declspec(noreturn) +#else +#define zig_noreturn +#endif + +#if defined(__GNUC__) +#define zig_unreachable() __builtin_unreachable() +#else +#define zig_unreachable() +#endif + +#if defined(_MSC_VER) +#define zig_breakpoint __debugbreak() +#else +#if defined(__MINGW32__) || defined(__MINGW64__) +#define zig_breakpoint __debugbreak() +#elif defined(__clang__) +#define zig_breakpoint __builtin_debugtrap() +#elif defined(__GNUC__) +#define zig_breakpoint __builtin_trap() +#elif defined(__i386__) || defined(__x86_64__) +#define zig_breakpoint __asm__ volatile("int $0x03"); +#else +#define zig_breakpoint raise(SIGTRAP) +#endif +#endif + +#include +#define int128_t __int128 +#define uint128_t unsigned __int128 +#include + diff --git a/src/link/cbe.h b/src/link/cbe.h deleted file mode 100644 index 8452af8fbc..0000000000 --- a/src/link/cbe.h +++ /dev/null @@ -1,44 +0,0 @@ -#if __STDC_VERSION__ >= 199901L -#include -#else -#define bool unsigned char -#define true 1 -#define false 0 -#endif - -#if __STDC_VERSION__ >= 201112L -#define zig_noreturn _Noreturn -#elif __GNUC__ -#define zig_noreturn __attribute__ ((noreturn)) -#elif _MSC_VER -#define zig_noreturn __declspec(noreturn) -#else -#define zig_noreturn -#endif - -#if defined(__GNUC__) -#define zig_unreachable() __builtin_unreachable() -#else -#define zig_unreachable() -#endif - -#if defined(_MSC_VER) -#define zig_breakpoint __debugbreak() -#else -#if defined(__MINGW32__) || defined(__MINGW64__) -#define zig_breakpoint __debugbreak() -#elif defined(__clang__) -#define zig_breakpoint __builtin_debugtrap() -#elif defined(__GNUC__) -#define zig_breakpoint __builtin_trap() -#elif defined(__i386__) || defined(__x86_64__) -#define zig_breakpoint __asm__ volatile("int $0x03"); -#else -#define zig_breakpoint raise(SIGTRAP) -#endif -#endif - -#include -#define int128_t __int128 -#define uint128_t unsigned __int128 -#include diff --git a/src/test.zig b/src/test.zig index 67a30f1f32..f630898189 100644 --- a/src/test.zig +++ b/src/test.zig @@ -13,7 +13,7 @@ const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_d const ThreadPool = @import("ThreadPool.zig"); const CrossTarget = std.zig.CrossTarget; -const c_header = @embedFile("link/cbe.h"); +const zig_h = link.File.C.zig_h; test "self-hosted" { var ctx = TestContext.init(); @@ -324,11 +324,11 @@ pub const TestContext = struct { } pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target, .Zig).addCompareObjectFile(src, c_header ++ out); + ctx.addC(name, target, .Zig).addCompareObjectFile(src, zig_h ++ out); } pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target, .Zig).addHeader(src, c_header ++ out); + ctx.addC(name, target, .Zig).addHeader(src, zig_h ++ out); } pub fn addCompareOutput( @@ -700,11 +700,12 @@ pub const TestContext = struct { }, } } - if (comp.bin_file.cast(link.File.C)) |c_file| { - std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ - c_file.main.items, - }); - } + // TODO print generated C code + //if (comp.bin_file.cast(link.File.C)) |c_file| { + // std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ + // c_file.main.items, + // }); + //} std.debug.print("Test failed.\n", .{}); std.process.exit(1); } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index a740a8851a..9947c90f13 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -22,8 +22,6 @@ pub fn addCases(ctx: *TestContext) !void { , "hello world!" ++ std.cstr.line_sep); // Now change the message only - // TODO fix C backend not supporting updates - // https://github.com/ziglang/zig/issues/7589 case.addCompareOutput( \\extern fn puts(s: [*:0]const u8) c_int; \\export fn main() c_int { -- cgit v1.2.3 From 58cfaa5982fc6c216627fb6f2cfd0d0e4e89d92b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 13:06:32 -0700 Subject: stage2: C backend: adjust spaces around functions --- src/codegen/c.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index d87801ae2e..ab66869d31 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -341,9 +341,10 @@ pub fn genDecl(o: *Object) !void { const func: *Module.Fn = func_payload.data; const instructions = func.body.instructions; const writer = o.code.writer(); + try writer.writeAll("\n"); try o.dg.renderFunctionSignature(writer); if (instructions.len == 0) { - try writer.writeAll(" {}\n\n"); + try writer.writeAll(" {}\n"); return; } @@ -382,7 +383,7 @@ pub fn genDecl(o: *Object) !void { } } - try writer.writeAll("}\n\n"); + try writer.writeAll("}\n"); } else if (tv.val.tag() == .extern_fn) { const writer = o.code.writer(); try o.dg.renderFunctionSignature(writer); -- cgit v1.2.3 From cd95444e4729761033f35d689a3b6ad6f4630552 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 13:59:33 -0700 Subject: stage2: C backend: remove format() hackery All C backend tests passing now, except for emit-h tests. Next task in the branch is to restore emit-h. --- src/codegen/c.zig | 131 ++++++++++++---------- src/link/C.zig | 10 +- src/link/C/zig.h | 27 +++-- test/stage2/cbe.zig | 313 ++++++++++++++++------------------------------------ 4 files changed, 187 insertions(+), 294 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index ab66869d31..5e274e0351 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -27,42 +27,6 @@ pub const CValue = union(enum) { arg: usize, /// By-value decl: *Decl, - - pub fn printed(value: CValue, object: *Object) Printed { - return .{ - .value = value, - .object = object, - }; - } - - pub const Printed = struct { - value: CValue, - object: *Object, - - /// TODO this got unwieldly, I want to remove the ability to print this way - pub fn format( - self: Printed, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) error{OutOfMemory}!void { - if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'"); - switch (self.value) { - .none => unreachable, - .local => |i| return std.fmt.format(writer, "t{d}", .{i}), - .local_ref => |i| return std.fmt.format(writer, "&t{d}", .{i}), - .constant => |inst| { - const o = self.object; - o.dg.renderValue(writer, inst.ty, inst.value().?) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => return, - }; - }, - .arg => |i| return std.fmt.format(writer, "a{d}", .{i}), - .decl => |decl| return writer.writeAll(mem.span(decl.name)), - } - } - }; }; pub const CValueMap = std.AutoHashMap(*Inst, CValue); @@ -103,6 +67,17 @@ pub const Object = struct { try o.code.writer().writeByteNTimes(' ', indent_amt); } + fn writeCValue(o: *Object, writer: Writer, c_value: CValue) !void { + switch (c_value) { + .none => unreachable, + .local => |i| return writer.print("t{d}", .{i}), + .local_ref => |i| return writer.print("&t{d}", .{i}), + .constant => |inst| return o.dg.renderValue(writer, inst.ty, inst.value().?), + .arg => |i| return writer.print("a{d}", .{i}), + .decl => |decl| return writer.writeAll(mem.span(decl.name)), + } + } + fn renderTypeAndName( o: *Object, writer: Writer, @@ -127,7 +102,9 @@ pub const Object = struct { .Const => "const ", .Mut => "", }; - try writer.print(" {s}{}{s}", .{ const_prefix, name.printed(o), suffix.items }); + try writer.print(" {s}", .{const_prefix}); + try o.writeCValue(writer, name); + try writer.writeAll(suffix.items); } }; @@ -353,7 +330,7 @@ pub fn genDecl(o: *Object) !void { try writer.writeAll("\n"); for (instructions) |inst| { const result_value = switch (inst.tag) { - .add => try genBinOp(o, inst.castTag(.add).?, "+"), + .add => try genBinOp(o, inst.castTag(.add).?, " + "), .alloc => try genAlloc(o, inst.castTag(.alloc).?), .arg => genArg(o), .assembly => try genAsm(o, inst.castTag(.assembly).?), @@ -361,19 +338,19 @@ pub fn genDecl(o: *Object) !void { .bitcast => try genBitcast(o, inst.castTag(.bitcast).?), .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?), .call => try genCall(o, inst.castTag(.call).?), - .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, "=="), - .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, ">"), - .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, ">="), - .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, "<"), - .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, "<="), - .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, "!="), + .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "), + .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "), + .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "), + .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "), + .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "), + .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "), .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?), .intcast => try genIntCast(o, inst.castTag(.intcast).?), .load => try genLoad(o, inst.castTag(.load).?), .ret => try genRet(o, inst.castTag(.ret).?), .retvoid => try genRetVoid(o), .store => try genStore(o, inst.castTag(.store).?), - .sub => try genBinOp(o, inst.castTag(.sub).?, "-"), + .sub => try genBinOp(o, inst.castTag(.sub).?, " - "), .unreach => try genUnreach(o, inst.castTag(.unreach).?), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; @@ -457,10 +434,14 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { switch (operand) { .local_ref => |i| { const wrapped: CValue = .{ .local = i }; - try writer.print(" = {};\n", .{wrapped.printed(o)}); + try writer.writeAll(" = "); + try o.writeCValue(writer, wrapped); + try writer.writeAll(";\n"); }, else => { - try writer.print(" = *{};\n", .{operand.printed(o)}); + try writer.writeAll(" = *"); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); }, } return local; @@ -469,7 +450,10 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { fn genRet(o: *Object, inst: *Inst.UnOp) !CValue { const operand = try o.resolveInst(inst.operand); try o.indent(); - try o.code.writer().print("return {};\n", .{operand.printed(o)}); + const writer = o.code.writer(); + try writer.writeAll("return "); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); return CValue.none; } @@ -484,7 +468,9 @@ fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue { const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); try o.dg.renderType(writer, inst.base.ty); - try writer.print("){};\n", .{from.printed(o)}); + try writer.writeAll(")"); + try o.writeCValue(writer, from); + try writer.writeAll(";\n"); return local; } @@ -498,10 +484,17 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue { switch (dest_ptr) { .local_ref => |i| { const dest: CValue = .{ .local = i }; - try writer.print("{} = {};\n", .{ dest.printed(o), src_val.printed(o) }); + try o.writeCValue(writer, dest); + try writer.writeAll(" = "); + try o.writeCValue(writer, src_val); + try writer.writeAll(";\n"); }, else => { - try writer.print("*{} = {};\n", .{ dest_ptr.printed(o), src_val.printed(o) }); + try writer.writeAll("*"); + try o.writeCValue(writer, dest_ptr); + try writer.writeAll(" = "); + try o.writeCValue(writer, src_val); + try writer.writeAll(";\n"); }, } return CValue.none; @@ -517,7 +510,13 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { try o.indent(); const writer = o.code.writer(); const local = try o.allocLocal(inst.base.ty, .Const); - try writer.print(" = {} {s} {};\n", .{ lhs.printed(o), operator, rhs.printed(o) }); + + try writer.writeAll(" = "); + try o.writeCValue(writer, lhs); + try writer.writeAll(operator); + try o.writeCValue(writer, rhs); + try writer.writeAll(";\n"); + return local; } @@ -556,7 +555,7 @@ fn genCall(o: *Object, inst: *Inst.Call) !CValue { try o.dg.renderValue(writer, arg.ty, val); } else { const val = try o.resolveInst(arg); - try writer.print("{}", .{val.printed(o)}); + try o.writeCValue(writer, val); } } } @@ -585,16 +584,25 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); try o.dg.renderType(writer, inst.base.ty); - try writer.print("){};\n", .{operand.printed(o)}); + + try writer.writeAll(")"); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); return local; } const local = try o.allocLocal(inst.base.ty, .Mut); try writer.writeAll(";\n"); try o.indent(); - try writer.print("memcpy(&{}, &{}, sizeof {});\n", .{ - local.printed(o), operand.printed(o), local.printed(o), - }); + + try writer.writeAll("memcpy(&"); + try o.writeCValue(writer, local); + try writer.writeAll(", &"); + try o.writeCValue(writer, operand); + try writer.writeAll(", sizeof "); + try o.writeCValue(writer, local); + try writer.writeAll(");\n"); + return local; } @@ -623,9 +631,10 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { try o.indent(); try writer.writeAll("register "); try o.dg.renderType(writer, arg.ty); - try writer.print(" {s}_constant __asm__(\"{s}\") = {};\n", .{ - reg, reg, arg_c_value.printed(o), - }); + + try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); + try o.writeCValue(writer, arg_c_value); + try writer.writeAll(";\n"); } else { return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{}); } @@ -648,7 +657,7 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (index > 0) { try writer.writeAll(", "); } - try writer.print("\"\"({s}_constant)", .{reg}); + try writer.print("\"r\"({s}_constant)", .{reg}); } else { // This is blocked by the earlier test unreachable; diff --git a/src/link/C.zig b/src/link/C.zig index 68eb56c5b9..0bca77f25f 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -101,14 +101,12 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { defer object.dg.fwd_decl.deinit(); codegen.genDecl(&object) catch |err| switch (err) { - error.AnalysisFail => {}, + error.AnalysisFail => { + try module.failed_decls.put(module.gpa, decl, object.dg.error_msg.?); + return; + }, else => |e| return e, }; - // The code may populate this error without returning error.AnalysisFail. - if (object.dg.error_msg) |msg| { - try module.failed_decls.put(module.gpa, decl, msg); - return; - } fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); code.* = object.code.moveToUnmanaged(); diff --git a/src/link/C/zig.h b/src/link/C/zig.h index 49f97210eb..fed799d348 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -22,24 +22,31 @@ #define zig_unreachable() #endif -#if defined(_MSC_VER) -#define zig_breakpoint __debugbreak() +#if __STDC_VERSION__ >= 199901L +#define zig_restrict restrict +#elif defined(__GNUC__) +#define zig_restrict __restrict #else -#if defined(__MINGW32__) || defined(__MINGW64__) -#define zig_breakpoint __debugbreak() +#define zig_restrict +#endif + +#if defined(_MSC_VER) +#define zig_breakpoint() __debugbreak() +#elif defined(__MINGW32__) || defined(__MINGW64__) +#define zig_breakpoint() __debugbreak() #elif defined(__clang__) -#define zig_breakpoint __builtin_debugtrap() +#define zig_breakpoint() __builtin_debugtrap() #elif defined(__GNUC__) -#define zig_breakpoint __builtin_trap() +#define zig_breakpoint() __builtin_trap() #elif defined(__i386__) || defined(__x86_64__) -#define zig_breakpoint __asm__ volatile("int $0x03"); +#define zig_breakpoint() __asm__ volatile("int $0x03"); #else -#define zig_breakpoint raise(SIGTRAP) -#endif +#define zig_breakpoint() raise(SIGTRAP) #endif #include +#include #define int128_t __int128 #define uint128_t unsigned __int128 -#include +void *memcpy (void *zig_restrict, const void *zig_restrict, size_t); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 9947c90f13..e66dabc147 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -31,6 +31,100 @@ pub fn addCases(ctx: *TestContext) !void { , "yo" ++ std.cstr.line_sep); } + { + var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64); + + // Exit with 0 + case.addCompareOutput( + \\fn exitGood() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ ); + \\ unreachable; + \\} + \\ + \\export fn main() c_int { + \\ exitGood(); + \\} + , ""); + + // Pass a usize parameter to exit + case.addCompareOutput( + \\export fn main() c_int { + \\ exit(0); + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); + + // Change the parameter to u8 + case.addCompareOutput( + \\export fn main() c_int { + \\ exit(0); + \\} + \\ + \\fn exit(code: u8) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); + + // Do some arithmetic at the exit callsite + case.addCompareOutput( + \\export fn main() c_int { + \\ exitMath(1); + \\} + \\ + \\fn exitMath(a: u8) noreturn { + \\ exit(0 + a - a); + \\} + \\ + \\fn exit(code: u8) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + \\ + , ""); + + // Invert the arithmetic + case.addCompareOutput( + \\export fn main() c_int { + \\ exitMath(1); + \\} + \\ + \\fn exitMath(a: u8) noreturn { + \\ exit(a + 0 - a); + \\} + \\ + \\fn exit(code: u8) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + \\ + , ""); + } + { var case = ctx.exeFromCompiledC("alloc and retptr", .{}); @@ -86,6 +180,8 @@ pub fn addCases(ctx: *TestContext) !void { \\ unreachable; \\} , + \\zig_noreturn void _start(void); + \\ \\zig_noreturn void _start(void) { \\ zig_breakpoint(); \\ zig_unreachable(); @@ -98,223 +194,6 @@ pub fn addCases(ctx: *TestContext) !void { \\void start(void); \\ ); - ctx.c("less empty start function", linux_x64, - \\fn main() noreturn { - \\ unreachable; - \\} - \\ - \\export fn _start() noreturn { - \\ main(); - \\} - , - \\static zig_noreturn void main(void); - \\ - \\static zig_noreturn void main(void) { - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - \\zig_noreturn void _start(void) { - \\ main(); - \\} - \\ - ); - // TODO: implement return values - // TODO: figure out a way to prevent asm constants from being generated - ctx.c("inline asm", linux_x64, - \\fn exitGood() noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (0) - \\ ); - \\ unreachable; - \\} - \\ - \\export fn _start() noreturn { - \\ exitGood(); - \\} - , - \\static zig_noreturn void exitGood(void); - \\ - \\static uint8_t exitGood__anon_0[6] = "{rax}"; - \\static uint8_t exitGood__anon_1[6] = "{rdi}"; - \\static uint8_t exitGood__anon_2[8] = "syscall"; - \\ - \\static zig_noreturn void exitGood(void) { - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = 0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - \\zig_noreturn void _start(void) { - \\ exitGood(); - \\} - \\ - ); - ctx.c("exit with parameter", linux_x64, - \\export fn _start() noreturn { - \\ exit(0); - \\} - \\ - \\fn exit(code: usize) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , - \\static zig_noreturn void exit(uintptr_t arg0); - \\ - \\static uint8_t exit__anon_0[6] = "{rax}"; - \\static uint8_t exit__anon_1[6] = "{rdi}"; - \\static uint8_t exit__anon_2[8] = "syscall"; - \\ - \\zig_noreturn void _start(void) { - \\ exit(0); - \\} - \\ - \\static zig_noreturn void exit(uintptr_t arg0) { - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = arg0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - ); - ctx.c("exit with u8 parameter", linux_x64, - \\export fn _start() noreturn { - \\ exit(0); - \\} - \\ - \\fn exit(code: u8) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , - \\static zig_noreturn void exit(uint8_t arg0); - \\ - \\static uint8_t exit__anon_0[6] = "{rax}"; - \\static uint8_t exit__anon_1[6] = "{rdi}"; - \\static uint8_t exit__anon_2[8] = "syscall"; - \\ - \\zig_noreturn void _start(void) { - \\ exit(0); - \\} - \\ - \\static zig_noreturn void exit(uint8_t arg0) { - \\ uintptr_t const __temp_0 = (uintptr_t)arg0; - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - ); - ctx.c("exit with u8 arithmetic", linux_x64, - \\export fn _start() noreturn { - \\ exitMath(1); - \\} - \\ - \\fn exitMath(a: u8) noreturn { - \\ exit(0 + a - a); - \\} - \\ - \\fn exit(code: u8) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , - \\static zig_noreturn void exitMath(uint8_t arg0); - \\static zig_noreturn void exit(uint8_t arg0); - \\ - \\static uint8_t exit__anon_0[6] = "{rax}"; - \\static uint8_t exit__anon_1[6] = "{rdi}"; - \\static uint8_t exit__anon_2[8] = "syscall"; - \\ - \\zig_noreturn void _start(void) { - \\ exitMath(1); - \\} - \\ - \\static zig_noreturn void exitMath(uint8_t arg0) { - \\ uint8_t const __temp_0 = 0 + arg0; - \\ uint8_t const __temp_1 = __temp_0 - arg0; - \\ exit(__temp_1); - \\} - \\ - \\static zig_noreturn void exit(uint8_t arg0) { - \\ uintptr_t const __temp_0 = (uintptr_t)arg0; - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - ); - ctx.c("exit with u8 arithmetic inverted", linux_x64, - \\export fn _start() noreturn { - \\ exitMath(1); - \\} - \\ - \\fn exitMath(a: u8) noreturn { - \\ exit(a + 0 - a); - \\} - \\ - \\fn exit(code: u8) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , - \\static zig_noreturn void exitMath(uint8_t arg0); - \\static zig_noreturn void exit(uint8_t arg0); - \\ - \\static uint8_t exit__anon_0[6] = "{rax}"; - \\static uint8_t exit__anon_1[6] = "{rdi}"; - \\static uint8_t exit__anon_2[8] = "syscall"; - \\ - \\zig_noreturn void _start(void) { - \\ exitMath(1); - \\} - \\ - \\static zig_noreturn void exitMath(uint8_t arg0) { - \\ uint8_t const __temp_0 = arg0 + 0; - \\ uint8_t const __temp_1 = __temp_0 - arg0; - \\ exit(__temp_1); - \\} - \\ - \\static zig_noreturn void exit(uint8_t arg0) { - \\ uintptr_t const __temp_0 = (uintptr_t)arg0; - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - ); ctx.h("header with single param function", linux_x64, \\export fn start(a: u8) void{} , -- cgit v1.2.3 From 1a2dd85570a6439f82f7a0dd2cbf452198168c5a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 17:33:31 -0700 Subject: stage2: C backend: re-implement emit-h and also mark functions as `extern "C"` as appropriate to support c++ compilers. --- src/Compilation.zig | 96 +++++++++++++++++++++++++++++++++++++++++------------ src/Module.zig | 73 +++++++++++++++++++++++++++++++++++----- src/codegen/c.zig | 60 ++++++++++++++++++--------------- src/link/C.zig | 46 +++++++++++++++++++++++-- src/link/C/zig.h | 14 +++++--- test/stage2/cbe.zig | 24 +++++++------- 6 files changed, 239 insertions(+), 74 deletions(-) (limited to 'src/codegen') diff --git a/src/Compilation.zig b/src/Compilation.zig index c654833270..552c550149 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -27,7 +27,6 @@ const Cache = @import("Cache.zig"); const stage1 = @import("stage1.zig"); const translate_c = @import("translate_c.zig"); const c_codegen = @import("codegen/c.zig"); -const c_link = @import("link/C.zig"); const ThreadPool = @import("ThreadPool.zig"); const WaitGroup = @import("WaitGroup.zig"); const libtsan = @import("libtsan.zig"); @@ -162,6 +161,8 @@ pub const CSourceFile = struct { const Job = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Module.Decl, + /// Render the .h file snippet for the Decl. + emit_h_decl: *Module.Decl, /// The Decl needs to be analyzed and possibly export itself. /// It may have already be analyzed, or it may have been determined /// to be outdated; in this case perform semantic analysis again. @@ -1312,9 +1313,14 @@ pub fn update(self: *Compilation) !void { // This is needed before reading the error flags. try self.bin_file.flush(self); - self.link_error_flags = self.bin_file.errorFlags(); + if (!use_stage1) { + if (self.bin_file.options.module) |module| { + try link.File.C.flushEmitH(module); + } + } + // If there are any errors, we anticipate the source files being loaded // to report error messages. Otherwise we unload all source files to save memory. if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { @@ -1340,7 +1346,8 @@ pub fn totalErrorCount(self: *Compilation) usize { var total: usize = self.failed_c_objects.items().len; if (self.bin_file.options.module) |module| { - total += module.failed_decls.items().len + + total += module.failed_decls.count() + + module.emit_h_failed_decls.count() + module.failed_exports.items().len + module.failed_files.items().len + @boolToInt(module.failed_root_src_file != null); @@ -1379,6 +1386,12 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { const source = try decl.scope.getSource(module); try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); } + for (module.emit_h_failed_decls.items()) |entry| { + const decl = entry.key; + const err_msg = entry.value; + const source = try decl.scope.getSource(module); + try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + } for (module.failed_exports.items()) |entry| { const decl = entry.key.owner_decl; const err_msg = entry.value; @@ -1476,27 +1489,68 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); - self.bin_file.updateDecl(module, decl) catch |err| { - switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - decl.analysis = .codegen_failure; - }, - else => { - try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - module.gpa, - decl.src(), - "unable to codegen: {s}", - .{@errorName(err)}, - )); - decl.analysis = .codegen_failure_retryable; - }, - } - return; + self.bin_file.updateDecl(module, decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + decl.analysis = .codegen_failure; + continue; + }, + else => { + try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); + module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.gpa, + decl.src(), + "unable to codegen: {s}", + .{@errorName(err)}, + )); + decl.analysis = .codegen_failure_retryable; + continue; + }, }; }, }, + .emit_h_decl => |decl| switch (decl.analysis) { + .unreferenced => unreachable, + .in_progress => unreachable, + .outdated => unreachable, + + .sema_failure, + .dependency_failure, + .sema_failure_retryable, + => continue, + + // emit-h only requires semantic analysis of the Decl to be complete, + // it does not depend on machine code generation to succeed. + .codegen_failure, .codegen_failure_retryable, .complete => { + if (build_options.omit_stage2) + @panic("sadly stage2 is omitted from this build to save memory on the CI server"); + const module = self.bin_file.options.module.?; + const emit_loc = module.emit_h.?; + const tv = decl.typed_value.most_recent.typed_value; + const emit_h = decl.getEmitH(module); + const fwd_decl = &emit_h.fwd_decl; + fwd_decl.shrinkRetainingCapacity(0); + + var dg: c_codegen.DeclGen = .{ + .module = module, + .error_msg = null, + .decl = decl, + .fwd_decl = fwd_decl.toManaged(module.gpa), + }; + defer dg.fwd_decl.deinit(); + + c_codegen.genHeader(&dg) catch |err| switch (err) { + error.AnalysisFail => { + try module.emit_h_failed_decls.put(module.gpa, decl, dg.error_msg.?); + continue; + }, + else => |e| return e, + }; + + fwd_decl.* = dg.fwd_decl.moveToUnmanaged(); + fwd_decl.shrink(module.gpa, fwd_decl.items.len); + }, + }, .analyze_decl => |decl| { if (build_options.omit_stage2) @panic("sadly stage2 is omitted from this build to save memory on the CI server"); diff --git a/src/Module.zig b/src/Module.zig index f1cec82680..6395a7f3a5 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -57,6 +57,10 @@ decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_has /// Note that a Decl can succeed but the Fn it represents can fail. In this case, /// a Decl can have a failed_decls entry but have analysis status of success. failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, +/// When emit_h is non-null, each Decl gets one more compile error slot for +/// emit-h failing for that Decl. This table is also how we tell if a Decl has +/// failed emit-h or succeeded. +emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, @@ -116,6 +120,13 @@ pub const Export = struct { }, }; +/// When Module emit_h field is non-null, each Decl is allocated via this struct, so that +/// there can be EmitH state attached to each Decl. +pub const DeclPlusEmitH = struct { + decl: Decl, + emit_h: EmitH, +}; + pub const Decl = struct { /// This name is relative to the containing namespace of the decl. It uses a null-termination /// to save bytes, since there can be a lot of decls in a compilation. The null byte is not allowed @@ -204,14 +215,21 @@ pub const Decl = struct { /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself` pub const DepsTable = std.ArrayHashMapUnmanaged(*Decl, void, std.array_hash_map.getAutoHashFn(*Decl), std.array_hash_map.getAutoEqlFn(*Decl), false); - pub fn destroy(self: *Decl, gpa: *Allocator) void { + pub fn destroy(self: *Decl, module: *Module) void { + const gpa = module.gpa; gpa.free(mem.spanZ(self.name)); if (self.typedValueManaged()) |tvm| { tvm.deinit(gpa); } self.dependants.deinit(gpa); self.dependencies.deinit(gpa); - gpa.destroy(self); + if (module.emit_h != null) { + const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", self); + decl_plus_emit_h.emit_h.fwd_decl.deinit(gpa); + gpa.destroy(decl_plus_emit_h); + } else { + gpa.destroy(self); + } } pub fn src(self: Decl) usize { @@ -277,6 +295,12 @@ pub const Decl = struct { return self.scope.cast(Scope.Container).?.file_scope; } + pub fn getEmitH(decl: *Decl, module: *Module) *EmitH { + assert(module.emit_h != null); + const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", decl); + return &decl_plus_emit_h.emit_h; + } + fn removeDependant(self: *Decl, other: *Decl) void { self.dependants.removeAssertDiscard(other); } @@ -286,6 +310,11 @@ pub const Decl = struct { } }; +/// This state is attached to every Decl when Module emit_h is non-null. +pub const EmitH = struct { + fwd_decl: std.ArrayListUnmanaged(u8) = .{}, +}; + /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. /// Extern functions do not have this data structure; they are represented by /// the `Decl` only, with a `Value` tag of `extern_fn`. @@ -883,7 +912,7 @@ pub fn deinit(self: *Module) void { self.deletion_set.deinit(gpa); for (self.decl_table.items()) |entry| { - entry.value.destroy(gpa); + entry.value.destroy(self); } self.decl_table.deinit(gpa); @@ -892,6 +921,11 @@ pub fn deinit(self: *Module) void { } self.failed_decls.deinit(gpa); + for (self.emit_h_failed_decls.items()) |entry| { + entry.value.destroy(gpa); + } + self.emit_h_failed_decls.deinit(gpa); + for (self.failed_files.items()) |entry| { entry.value.destroy(gpa); } @@ -1150,6 +1184,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { try self.comp.bin_file.allocateDeclIndexes(decl); try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); + if (type_changed and self.emit_h != null) { + try self.comp.work_queue.writeItem(.{ .emit_h_decl = decl }); + } + return type_changed; }; @@ -1269,6 +1307,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { // increasing how many computations can be done in parallel. try self.comp.bin_file.allocateDeclIndexes(decl); try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); + if (type_changed and self.emit_h != null) { + try self.comp.work_queue.writeItem(.{ .emit_h_decl = decl }); + } } else if (!prev_is_inline and prev_type_has_bits) { self.comp.bin_file.freeDecl(decl); } @@ -1837,9 +1878,13 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { if (self.failed_decls.remove(decl)) |entry| { entry.value.destroy(self.gpa); } + if (self.emit_h_failed_decls.remove(decl)) |entry| { + entry.value.destroy(self.gpa); + } self.deleteDeclExports(decl); self.comp.bin_file.freeDecl(decl); - decl.destroy(self.gpa); + + decl.destroy(self); } /// Delete all the Export objects that are caused by this Decl. Re-analysis of @@ -1923,16 +1968,28 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { if (self.failed_decls.remove(decl)) |entry| { entry.value.destroy(self.gpa); } + if (self.emit_h_failed_decls.remove(decl)) |entry| { + entry.value.destroy(self.gpa); + } decl.analysis = .outdated; } fn allocateNewDecl( - self: *Module, + mod: *Module, scope: *Scope, src_index: usize, contents_hash: std.zig.SrcHash, ) !*Decl { - const new_decl = try self.gpa.create(Decl); + // If we have emit-h then we must allocate a bigger structure to store the emit-h state. + const new_decl: *Decl = if (mod.emit_h != null) blk: { + const parent_struct = try mod.gpa.create(DeclPlusEmitH); + parent_struct.* = .{ + .emit_h = .{}, + .decl = undefined, + }; + break :blk &parent_struct.decl; + } else try mod.gpa.create(Decl); + new_decl.* = .{ .name = "", .scope = scope.namespace(), @@ -1941,14 +1998,14 @@ fn allocateNewDecl( .analysis = .unreferenced, .deletion_flag = false, .contents_hash = contents_hash, - .link = switch (self.comp.bin_file.tag) { + .link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = link.File.Coff.TextBlock.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, .macho => .{ .macho = link.File.MachO.TextBlock.empty }, .c => .{ .c = link.File.C.DeclBlock.empty }, .wasm => .{ .wasm = {} }, }, - .fn_link = switch (self.comp.bin_file.tag) { + .fn_link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = {} }, .elf => .{ .elf = link.File.Elf.SrcFn.empty }, .macho => .{ .macho = link.File.MachO.SrcFn.empty }, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 5e274e0351..3de79e2a3d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -9,6 +9,7 @@ const Compilation = @import("../Compilation.zig"); const Inst = @import("../ir.zig").Inst; const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; +const TypedValue = @import("../TypedValue.zig"); const C = link.File.C; const Decl = Module.Decl; const trace = @import("../tracy.zig").trace; @@ -109,7 +110,7 @@ pub const Object = struct { }; /// This data is available both when outputting .c code and when outputting an .h file. -const DeclGen = struct { +pub const DeclGen = struct { module: *Module, decl: *Decl, fwd_decl: std.ArrayList(u8), @@ -199,22 +200,11 @@ const DeclGen = struct { } } - fn renderFunctionSignature(dg: *DeclGen, w: Writer) !void { - const tv = dg.decl.typed_value.most_recent.typed_value; - // Determine whether the function is globally visible. - const is_global = blk: { - switch (tv.val.tag()) { - .extern_fn => break :blk true, - .function => { - const func = tv.val.castTag(.function).?.data; - break :blk dg.module.decl_exports.contains(func.owner_decl); - }, - else => unreachable, - } - }; + fn renderFunctionSignature(dg: *DeclGen, w: Writer, is_global: bool) !void { if (!is_global) { try w.writeAll("static "); } + const tv = dg.decl.typed_value.most_recent.typed_value; try dg.renderType(w, tv.ty.fnReturnType()); const decl_name = mem.span(dg.decl.name); try w.print(" {s}(", .{decl_name}); @@ -302,6 +292,17 @@ const DeclGen = struct { }), } } + + fn functionIsGlobal(dg: *DeclGen, tv: TypedValue) bool { + switch (tv.val.tag()) { + .extern_fn => return true, + .function => { + const func = tv.val.castTag(.function).?.data; + return dg.module.decl_exports.contains(func.owner_decl); + }, + else => unreachable, + } + } }; pub fn genDecl(o: *Object) !void { @@ -311,15 +312,19 @@ pub fn genDecl(o: *Object) !void { const tv = o.dg.decl.typed_value.most_recent.typed_value; if (tv.val.castTag(.function)) |func_payload| { + const is_global = o.dg.functionIsGlobal(tv); const fwd_decl_writer = o.dg.fwd_decl.writer(); - try o.dg.renderFunctionSignature(fwd_decl_writer); + if (is_global) { + try fwd_decl_writer.writeAll("ZIG_EXTERN_C "); + } + try o.dg.renderFunctionSignature(fwd_decl_writer, is_global); try fwd_decl_writer.writeAll(";\n"); const func: *Module.Fn = func_payload.data; const instructions = func.body.instructions; const writer = o.code.writer(); try writer.writeAll("\n"); - try o.dg.renderFunctionSignature(writer); + try o.dg.renderFunctionSignature(writer, is_global); if (instructions.len == 0) { try writer.writeAll(" {}\n"); return; @@ -363,7 +368,8 @@ pub fn genDecl(o: *Object) !void { try writer.writeAll("}\n"); } else if (tv.val.tag() == .extern_fn) { const writer = o.code.writer(); - try o.dg.renderFunctionSignature(writer); + try writer.writeAll("ZIG_EXTERN_C "); + try o.dg.renderFunctionSignature(writer, true); try writer.writeAll(";\n"); } else { const writer = o.code.writer(); @@ -381,20 +387,20 @@ pub fn genDecl(o: *Object) !void { } } -pub fn genHeader(comp: *Compilation, dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { +pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { const tracy = trace(@src()); defer tracy.end(); - switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { + const tv = dg.decl.typed_value.most_recent.typed_value; + const writer = dg.fwd_decl.writer(); + + switch (tv.ty.zigTypeTag()) { .Fn => { - dg.renderFunctionSignature() catch |err| switch (err) { - error.AnalysisFail => { - try dg.module.failed_decls.put(dg.module.gpa, decl, dg.error_msg.?); - dg.error_msg = null; - return error.AnalysisFail; - }, - else => |e| return e, - }; + const is_global = dg.functionIsGlobal(tv); + if (is_global) { + try writer.writeAll("ZIG_EXTERN_C "); + } + try dg.renderFunctionSignature(writer, is_global); try dg.fwd_decl.appendSlice(";\n"); }, else => {}, diff --git a/src/link/C.zig b/src/link/C.zig index 0bca77f25f..32d859f43e 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -130,8 +130,10 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - const module = self.base.options.module orelse - return error.LinkingWithoutZigSourceUnimplemented; + const module = self.base.options.module.?; + + // This code path happens exclusively with -ofmt=c. The flush logic for + // emit-h is in `flushEmitH` below. // We collect a list of buffers to write, and write them all at once with pwritev 😎 var all_buffers = std.ArrayList(std.os.iovec_const).init(comp.gpa); @@ -187,6 +189,46 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { try file.pwritevAll(all_buffers.items, 0); } +pub fn flushEmitH(module: *Module) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const emit_h_loc = module.emit_h orelse return; + + // We collect a list of buffers to write, and write them all at once with pwritev 😎 + var all_buffers = std.ArrayList(std.os.iovec_const).init(module.gpa); + defer all_buffers.deinit(); + + try all_buffers.ensureCapacity(module.decl_table.count() + 1); + + var file_size: u64 = zig_h.len; + all_buffers.appendAssumeCapacity(.{ + .iov_base = zig_h, + .iov_len = zig_h.len, + }); + + for (module.decl_table.items()) |kv| { + const emit_h = kv.value.getEmitH(module); + const buf = emit_h.fwd_decl.items; + all_buffers.appendAssumeCapacity(.{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }); + file_size += buf.len; + } + + const directory = emit_h_loc.directory orelse module.comp.local_cache_directory; + const file = try directory.handle.createFile(emit_h_loc.basename, .{ + // We set the end position explicitly below; by not truncating the file, we possibly + // make it easier on the file system by doing 1 reallocation instead of two. + .truncate = false, + }); + defer file.close(); + + try file.setEndPos(file_size); + try file.pwritevAll(all_buffers.items, 0); +} + pub fn updateDeclExports( self: *C, module: *Module, diff --git a/src/link/C/zig.h b/src/link/C/zig.h index fed799d348..640dfb2345 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -23,11 +23,17 @@ #endif #if __STDC_VERSION__ >= 199901L -#define zig_restrict restrict +#define ZIG_RESTRICT restrict #elif defined(__GNUC__) -#define zig_restrict __restrict +#define ZIG_RESTRICT __restrict #else -#define zig_restrict +#define ZIG_RESTRICT +#endif + +#ifdef __cplusplus +#define ZIG_EXTERN_C extern "C" +#else +#define ZIG_EXTERN_C #endif #if defined(_MSC_VER) @@ -48,5 +54,5 @@ #include #define int128_t __int128 #define uint128_t unsigned __int128 -void *memcpy (void *zig_restrict, const void *zig_restrict, size_t); +ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index e66dabc147..9163ac1662 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -180,7 +180,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ unreachable; \\} , - \\zig_noreturn void _start(void); + \\ZIG_EXTERN_C zig_noreturn void _start(void); \\ \\zig_noreturn void _start(void) { \\ zig_breakpoint(); @@ -191,37 +191,37 @@ pub fn addCases(ctx: *TestContext) !void { ctx.h("simple header", linux_x64, \\export fn start() void{} , - \\void start(void); + \\ZIG_EXTERN_C void start(void); \\ ); ctx.h("header with single param function", linux_x64, \\export fn start(a: u8) void{} , - \\void start(uint8_t arg0); + \\ZIG_EXTERN_C void start(uint8_t a0); \\ ); ctx.h("header with multiple param function", linux_x64, \\export fn start(a: u8, b: u8, c: u8) void{} , - \\void start(uint8_t arg0, uint8_t arg1, uint8_t arg2); + \\ZIG_EXTERN_C void start(uint8_t a0, uint8_t a1, uint8_t a2); \\ ); ctx.h("header with u32 param function", linux_x64, \\export fn start(a: u32) void{} , - \\void start(uint32_t arg0); + \\ZIG_EXTERN_C void start(uint32_t a0); \\ ); ctx.h("header with usize param function", linux_x64, \\export fn start(a: usize) void{} , - \\void start(uintptr_t arg0); + \\ZIG_EXTERN_C void start(uintptr_t a0); \\ ); ctx.h("header with bool param function", linux_x64, \\export fn start(a: bool) void{} , - \\void start(bool arg0); + \\ZIG_EXTERN_C void start(bool a0); \\ ); ctx.h("header with noreturn function", linux_x64, @@ -229,7 +229,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ unreachable; \\} , - \\zig_noreturn void start(void); + \\ZIG_EXTERN_C zig_noreturn void start(void); \\ ); ctx.h("header with multiple functions", linux_x64, @@ -237,15 +237,15 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn b() void{} \\export fn c() void{} , - \\void a(void); - \\void b(void); - \\void c(void); + \\ZIG_EXTERN_C void a(void); + \\ZIG_EXTERN_C void b(void); + \\ZIG_EXTERN_C void c(void); \\ ); ctx.h("header with multiple includes", linux_x64, \\export fn start(a: u32, b: usize) void{} , - \\void start(uint32_t arg0, uintptr_t arg1); + \\ZIG_EXTERN_C void start(uint32_t a0, uintptr_t a1); \\ ); } -- cgit v1.2.3 From b1cfa923bee5210fd78c7508d1af92dde3361c8c Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Wed, 6 Jan 2021 01:27:06 +0100 Subject: stage2: rename and move files related to LLVM backend --- CMakeLists.txt | 4 +- src/Compilation.zig | 2 +- src/codegen/llvm.zig | 722 ++++++++++++++++++++++++++++++++++++++++++ src/codegen/llvm/bindings.zig | 571 +++++++++++++++++++++++++++++++++ src/link.zig | 2 +- src/link/Coff.zig | 2 +- src/link/Elf.zig | 2 +- src/llvm_backend.zig | 722 ------------------------------------------ src/llvm_bindings.zig | 574 --------------------------------- src/main.zig | 6 +- src/mingw.zig | 2 +- src/target.zig | 2 +- test/stage2/llvm.zig | 43 +++ test/stage2/llvm_backend.zig | 43 --- test/stage2/test.zig | 2 +- 15 files changed, 1348 insertions(+), 1351 deletions(-) create mode 100644 src/codegen/llvm.zig create mode 100644 src/codegen/llvm/bindings.zig delete mode 100644 src/llvm_backend.zig delete mode 100644 src/llvm_bindings.zig create mode 100644 test/stage2/llvm.zig delete mode 100644 test/stage2/llvm_backend.zig (limited to 'src/codegen') diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ed25b93d2..eb4b465ef3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -541,6 +541,8 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/codegen/aarch64.zig" "${CMAKE_SOURCE_DIR}/src/codegen/arm.zig" "${CMAKE_SOURCE_DIR}/src/codegen/c.zig" + "${CMAKE_SOURCE_DIR}/src/codegen/llvm.zig" + "${CMAKE_SOURCE_DIR}/src/codegen/llvm/bindings.zig" "${CMAKE_SOURCE_DIR}/src/codegen/riscv64.zig" "${CMAKE_SOURCE_DIR}/src/codegen/spu-mk2.zig" "${CMAKE_SOURCE_DIR}/src/codegen/wasm.zig" @@ -562,8 +564,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/cbe.h" "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" "${CMAKE_SOURCE_DIR}/src/liveness.zig" - "${CMAKE_SOURCE_DIR}/src/llvm_backend.zig" - "${CMAKE_SOURCE_DIR}/src/llvm_bindings.zig" "${CMAKE_SOURCE_DIR}/src/main.zig" "${CMAKE_SOURCE_DIR}/src/mingw.zig" "${CMAKE_SOURCE_DIR}/src/musl.zig" diff --git a/src/Compilation.zig b/src/Compilation.zig index 9912520437..77f9f79bae 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2116,7 +2116,7 @@ pub fn addCCArgs( try argv.append("-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS"); } - const llvm_triple = try @import("llvm_backend.zig").targetTriple(arena, target); + const llvm_triple = try @import("codegen/llvm.zig").targetTriple(arena, target); try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple }); switch (ext) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig new file mode 100644 index 0000000000..df97f7e0ef --- /dev/null +++ b/src/codegen/llvm.zig @@ -0,0 +1,722 @@ +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const Compilation = @import("../Compilation.zig"); +const llvm = @import("llvm/bindings.zig"); +const link = @import("../link.zig"); +const log = std.log.scoped(.codegen); + +const Module = @import("../Module.zig"); +const TypedValue = @import("../TypedValue.zig"); +const ir = @import("../ir.zig"); +const Inst = ir.Inst; + +const Value = @import("../value.zig").Value; +const Type = @import("../type.zig").Type; + +pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { + const llvm_arch = switch (target.cpu.arch) { + .arm => "arm", + .armeb => "armeb", + .aarch64 => "aarch64", + .aarch64_be => "aarch64_be", + .aarch64_32 => "aarch64_32", + .arc => "arc", + .avr => "avr", + .bpfel => "bpfel", + .bpfeb => "bpfeb", + .hexagon => "hexagon", + .mips => "mips", + .mipsel => "mipsel", + .mips64 => "mips64", + .mips64el => "mips64el", + .msp430 => "msp430", + .powerpc => "powerpc", + .powerpc64 => "powerpc64", + .powerpc64le => "powerpc64le", + .r600 => "r600", + .amdgcn => "amdgcn", + .riscv32 => "riscv32", + .riscv64 => "riscv64", + .sparc => "sparc", + .sparcv9 => "sparcv9", + .sparcel => "sparcel", + .s390x => "s390x", + .tce => "tce", + .tcele => "tcele", + .thumb => "thumb", + .thumbeb => "thumbeb", + .i386 => "i386", + .x86_64 => "x86_64", + .xcore => "xcore", + .nvptx => "nvptx", + .nvptx64 => "nvptx64", + .le32 => "le32", + .le64 => "le64", + .amdil => "amdil", + .amdil64 => "amdil64", + .hsail => "hsail", + .hsail64 => "hsail64", + .spir => "spir", + .spir64 => "spir64", + .kalimba => "kalimba", + .shave => "shave", + .lanai => "lanai", + .wasm32 => "wasm32", + .wasm64 => "wasm64", + .renderscript32 => "renderscript32", + .renderscript64 => "renderscript64", + .ve => "ve", + .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, + }; + // TODO Add a sub-arch for some architectures depending on CPU features. + + const llvm_os = switch (target.os.tag) { + .freestanding => "unknown", + .ananas => "ananas", + .cloudabi => "cloudabi", + .dragonfly => "dragonfly", + .freebsd => "freebsd", + .fuchsia => "fuchsia", + .ios => "ios", + .kfreebsd => "kfreebsd", + .linux => "linux", + .lv2 => "lv2", + .macos => "macosx", + .netbsd => "netbsd", + .openbsd => "openbsd", + .solaris => "solaris", + .windows => "windows", + .haiku => "haiku", + .minix => "minix", + .rtems => "rtems", + .nacl => "nacl", + .cnk => "cnk", + .aix => "aix", + .cuda => "cuda", + .nvcl => "nvcl", + .amdhsa => "amdhsa", + .ps4 => "ps4", + .elfiamcu => "elfiamcu", + .tvos => "tvos", + .watchos => "watchos", + .mesa3d => "mesa3d", + .contiki => "contiki", + .amdpal => "amdpal", + .hermit => "hermit", + .hurd => "hurd", + .wasi => "wasi", + .emscripten => "emscripten", + .uefi => "windows", + .other => "unknown", + }; + + const llvm_abi = switch (target.abi) { + .none => "unknown", + .gnu => "gnu", + .gnuabin32 => "gnuabin32", + .gnuabi64 => "gnuabi64", + .gnueabi => "gnueabi", + .gnueabihf => "gnueabihf", + .gnux32 => "gnux32", + .code16 => "code16", + .eabi => "eabi", + .eabihf => "eabihf", + .android => "android", + .musl => "musl", + .musleabi => "musleabi", + .musleabihf => "musleabihf", + .msvc => "msvc", + .itanium => "itanium", + .cygnus => "cygnus", + .coreclr => "coreclr", + .simulator => "simulator", + .macabi => "macabi", + }; + + return std.fmt.allocPrintZ(allocator, "{s}-unknown-{s}-{s}", .{ llvm_arch, llvm_os, llvm_abi }); +} + +pub const LLVMIRModule = struct { + module: *Module, + llvm_module: *const llvm.Module, + context: *const llvm.Context, + target_machine: *const llvm.TargetMachine, + builder: *const llvm.Builder, + + object_path: []const u8, + + gpa: *Allocator, + err_msg: ?*Compilation.ErrorMsg = null, + + // TODO: The fields below should really move into a different struct, + // because they are only valid when generating a function + + /// This stores the LLVM values used in a function, such that they can be + /// referred to in other instructions. This table is cleared before every function is generated. + func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value) = .{}, + + /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction. + args: []*const llvm.Value = &[_]*const llvm.Value{}, + arg_index: usize = 0, + + entry_block: *const llvm.BasicBlock = undefined, + /// This fields stores the last alloca instruction, such that we can append more alloca instructions + /// to the top of the function. + latest_alloca_inst: ?*const llvm.Value = null, + + pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*LLVMIRModule { + const self = try allocator.create(LLVMIRModule); + errdefer allocator.destroy(self); + + const gpa = options.module.?.gpa; + + const obj_basename = try std.zig.binNameAlloc(gpa, .{ + .root_name = options.root_name, + .target = options.target, + .output_mode = .Obj, + }); + defer gpa.free(obj_basename); + + const o_directory = options.module.?.zig_cache_artifact_directory; + const object_path = try o_directory.join(gpa, &[_][]const u8{obj_basename}); + errdefer gpa.free(object_path); + + const context = llvm.Context.create(); + errdefer context.dispose(); + + initializeLLVMTargets(); + + const root_nameZ = try gpa.dupeZ(u8, options.root_name); + defer gpa.free(root_nameZ); + const llvm_module = llvm.Module.createWithName(root_nameZ.ptr, context); + errdefer llvm_module.dispose(); + + const llvm_target_triple = try targetTriple(gpa, options.target); + defer gpa.free(llvm_target_triple); + + var error_message: [*:0]const u8 = undefined; + var target: *const llvm.Target = undefined; + if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message)) { + defer llvm.disposeMessage(error_message); + + const stderr = std.io.getStdErr().outStream(); + try stderr.print( + \\Zig is expecting LLVM to understand this target: '{s}' + \\However LLVM responded with: "{s}" + \\Zig is unable to continue. This is a bug in Zig: + \\https://github.com/ziglang/zig/issues/438 + \\ + , + .{ + llvm_target_triple, + error_message, + }, + ); + return error.InvalidLLVMTriple; + } + + const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug) .None else .Aggressive; + const target_machine = llvm.TargetMachine.create( + target, + llvm_target_triple.ptr, + "", + "", + opt_level, + .Static, + .Default, + ); + errdefer target_machine.dispose(); + + const builder = context.createBuilder(); + errdefer builder.dispose(); + + self.* = .{ + .module = options.module.?, + .llvm_module = llvm_module, + .context = context, + .target_machine = target_machine, + .builder = builder, + .object_path = object_path, + .gpa = gpa, + }; + return self; + } + + pub fn deinit(self: *LLVMIRModule, allocator: *Allocator) void { + self.builder.dispose(); + self.target_machine.dispose(); + self.llvm_module.dispose(); + self.context.dispose(); + + self.func_inst_table.deinit(self.gpa); + self.gpa.free(self.object_path); + + allocator.destroy(self); + } + + fn initializeLLVMTargets() void { + llvm.initializeAllTargets(); + llvm.initializeAllTargetInfos(); + llvm.initializeAllTargetMCs(); + llvm.initializeAllAsmPrinters(); + llvm.initializeAllAsmParsers(); + } + + pub fn flushModule(self: *LLVMIRModule, comp: *Compilation) !void { + if (comp.verbose_llvm_ir) { + const dump = self.llvm_module.printToString(); + defer llvm.disposeMessage(dump); + + const stderr = std.io.getStdErr().outStream(); + try stderr.writeAll(std.mem.spanZ(dump)); + } + + { + var error_message: [*:0]const u8 = undefined; + // verifyModule always allocs the error_message even if there is no error + defer llvm.disposeMessage(error_message); + + if (self.llvm_module.verify(.ReturnStatus, &error_message)) { + const stderr = std.io.getStdErr().outStream(); + try stderr.print("broken LLVM module found: {s}\nThis is a bug in the Zig compiler.", .{error_message}); + return error.BrokenLLVMModule; + } + } + + const object_pathZ = try self.gpa.dupeZ(u8, self.object_path); + defer self.gpa.free(object_pathZ); + + var error_message: [*:0]const u8 = undefined; + if (self.target_machine.emitToFile( + self.llvm_module, + object_pathZ.ptr, + .ObjectFile, + &error_message, + )) { + defer llvm.disposeMessage(error_message); + + const stderr = std.io.getStdErr().outStream(); + try stderr.print("LLVM failed to emit file: {s}\n", .{error_message}); + return error.FailedToEmit; + } + } + + pub fn updateDecl(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { + self.gen(module, decl) catch |err| switch (err) { + error.CodegenFail => { + decl.analysis = .codegen_failure; + try module.failed_decls.put(module.gpa, decl, self.err_msg.?); + self.err_msg = null; + return; + }, + else => |e| return e, + }; + } + + fn gen(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { + const typed_value = decl.typed_value.most_recent.typed_value; + const src = decl.src(); + + log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val }); + + if (typed_value.val.castTag(.function)) |func_payload| { + const func = func_payload.data; + + const llvm_func = try self.resolveLLVMFunction(func.owner_decl, src); + + // This gets the LLVM values from the function and stores them in `self.args`. + const fn_param_len = func.owner_decl.typed_value.most_recent.typed_value.ty.fnParamLen(); + var args = try self.gpa.alloc(*const llvm.Value, fn_param_len); + defer self.gpa.free(args); + + for (args) |*arg, i| { + arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i)); + } + self.args = args; + self.arg_index = 0; + + // Make sure no other LLVM values from other functions can be referenced + self.func_inst_table.clearRetainingCapacity(); + + // We remove all the basic blocks of a function to support incremental + // compilation! + // TODO: remove all basic blocks if functions can have more than one + if (llvm_func.getFirstBasicBlock()) |bb| { + bb.deleteBasicBlock(); + } + + self.entry_block = self.context.appendBasicBlock(llvm_func, "Entry"); + self.builder.positionBuilderAtEnd(self.entry_block); + self.latest_alloca_inst = null; + + const instructions = func.body.instructions; + for (instructions) |inst| { + const opt_llvm_val: ?*const llvm.Value = switch (inst.tag) { + .add => try self.genAdd(inst.castTag(.add).?), + .alloc => try self.genAlloc(inst.castTag(.alloc).?), + .arg => try self.genArg(inst.castTag(.arg).?), + .bitcast => try self.genBitCast(inst.castTag(.bitcast).?), + .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?), + .call => try self.genCall(inst.castTag(.call).?), + .intcast => try self.genIntCast(inst.castTag(.intcast).?), + .load => try self.genLoad(inst.castTag(.load).?), + .not => try self.genNot(inst.castTag(.not).?), + .ret => try self.genRet(inst.castTag(.ret).?), + .retvoid => self.genRetVoid(inst.castTag(.retvoid).?), + .store => try self.genStore(inst.castTag(.store).?), + .sub => try self.genSub(inst.castTag(.sub).?), + .unreach => self.genUnreach(inst.castTag(.unreach).?), + .dbg_stmt => blk: { + // TODO: implement debug info + break :blk null; + }, + else => |tag| return self.fail(src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}), + }; + if (opt_llvm_val) |llvm_val| try self.func_inst_table.putNoClobber(self.gpa, inst, llvm_val); + } + } else if (typed_value.val.castTag(.extern_fn)) |extern_fn| { + _ = try self.resolveLLVMFunction(extern_fn.data, src); + } else { + _ = try self.resolveGlobalDecl(decl, src); + } + } + + fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !?*const llvm.Value { + if (inst.func.value()) |func_value| { + const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn| + extern_fn.data + else if (func_value.castTag(.function)) |func_payload| + func_payload.data.owner_decl + else + unreachable; + + const zig_fn_type = fn_decl.typed_value.most_recent.typed_value.ty; + const llvm_fn = try self.resolveLLVMFunction(fn_decl, inst.base.src); + + const num_args = inst.args.len; + + const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, num_args); + defer self.gpa.free(llvm_param_vals); + + for (inst.args) |arg, i| { + llvm_param_vals[i] = try self.resolveInst(arg); + } + + // TODO: LLVMBuildCall2 handles opaque function pointers, according to llvm docs + // Do we need that? + const call = self.builder.buildCall( + llvm_fn, + if (num_args == 0) null else llvm_param_vals.ptr, + @intCast(c_uint, num_args), + "", + ); + + const return_type = zig_fn_type.fnReturnType(); + if (return_type.tag() == .noreturn) { + _ = self.builder.buildUnreachable(); + } + + // No need to store the LLVM value if the return type is void or noreturn + if (!return_type.hasCodeGenBits()) return null; + + return call; + } else { + return self.fail(inst.base.src, "TODO implement calling runtime known function pointer LLVM backend", .{}); + } + } + + fn genRetVoid(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value { + _ = self.builder.buildRetVoid(); + return null; + } + + fn genRet(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + _ = self.builder.buildRet(try self.resolveInst(inst.operand)); + return null; + } + + fn genNot(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + return self.builder.buildNot(try self.resolveInst(inst.operand), ""); + } + + fn genUnreach(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value { + _ = self.builder.buildUnreachable(); + return null; + } + + fn genAdd(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { + const lhs = try self.resolveInst(inst.lhs); + const rhs = try self.resolveInst(inst.rhs); + + if (!inst.base.ty.isInt()) + return self.fail(inst.base.src, "TODO implement 'genAdd' for type {}", .{inst.base.ty}); + + return if (inst.base.ty.isSignedInt()) + self.builder.buildNSWAdd(lhs, rhs, "") + else + self.builder.buildNUWAdd(lhs, rhs, ""); + } + + fn genSub(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { + const lhs = try self.resolveInst(inst.lhs); + const rhs = try self.resolveInst(inst.rhs); + + if (!inst.base.ty.isInt()) + return self.fail(inst.base.src, "TODO implement 'genSub' for type {}", .{inst.base.ty}); + + return if (inst.base.ty.isSignedInt()) + self.builder.buildNSWSub(lhs, rhs, "") + else + self.builder.buildNUWSub(lhs, rhs, ""); + } + + fn genIntCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + const val = try self.resolveInst(inst.operand); + + const signed = inst.base.ty.isSignedInt(); + // TODO: Should we use intcast here or just a simple bitcast? + // LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes + return self.builder.buildIntCast2(val, try self.getLLVMType(inst.base.ty, inst.base.src), signed, ""); + } + + fn genBitCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + const val = try self.resolveInst(inst.operand); + const dest_type = try self.getLLVMType(inst.base.ty, inst.base.src); + + return self.builder.buildBitCast(val, dest_type, ""); + } + + fn genArg(self: *LLVMIRModule, inst: *Inst.Arg) !?*const llvm.Value { + const arg_val = self.args[self.arg_index]; + self.arg_index += 1; + + const ptr_val = self.buildAlloca(try self.getLLVMType(inst.base.ty, inst.base.src)); + _ = self.builder.buildStore(arg_val, ptr_val); + return self.builder.buildLoad(ptr_val, ""); + } + + fn genAlloc(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.Value { + // buildAlloca expects the pointee type, not the pointer type, so assert that + // a Payload.PointerSimple is passed to the alloc instruction. + const pointee_type = inst.base.ty.castPointer().?.data; + + // TODO: figure out a way to get the name of the var decl. + // TODO: set alignment and volatile + return self.buildAlloca(try self.getLLVMType(pointee_type, inst.base.src)); + } + + /// Use this instead of builder.buildAlloca, because this function makes sure to + /// put the alloca instruction at the top of the function! + fn buildAlloca(self: *LLVMIRModule, t: *const llvm.Type) *const llvm.Value { + if (self.latest_alloca_inst) |latest_alloc| { + // builder.positionBuilder adds it before the instruction, + // but we want to put it after the last alloca instruction. + self.builder.positionBuilder(self.entry_block, latest_alloc.getNextInstruction().?); + } else { + // There might have been other instructions emitted before the + // first alloca has been generated. However the alloca should still + // be first in the function. + if (self.entry_block.getFirstInstruction()) |first_inst| { + self.builder.positionBuilder(self.entry_block, first_inst); + } + } + defer self.builder.positionBuilderAtEnd(self.entry_block); + + const val = self.builder.buildAlloca(t, ""); + self.latest_alloca_inst = val; + return val; + } + + fn genStore(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { + const val = try self.resolveInst(inst.rhs); + const ptr = try self.resolveInst(inst.lhs); + _ = self.builder.buildStore(val, ptr); + return null; + } + + fn genLoad(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { + const ptr_val = try self.resolveInst(inst.operand); + return self.builder.buildLoad(ptr_val, ""); + } + + fn genBreakpoint(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.Value { + const llvn_fn = self.getIntrinsic("llvm.debugtrap"); + _ = self.builder.buildCall(llvn_fn, null, 0, ""); + return null; + } + + fn getIntrinsic(self: *LLVMIRModule, name: []const u8) *const llvm.Value { + const id = llvm.lookupIntrinsicID(name.ptr, name.len); + assert(id != 0); + // TODO: add support for overload intrinsics by passing the prefix of the intrinsic + // to `lookupIntrinsicID` and then passing the correct types to + // `getIntrinsicDeclaration` + return self.llvm_module.getIntrinsicDeclaration(id, null, 0); + } + + fn resolveInst(self: *LLVMIRModule, inst: *ir.Inst) !*const llvm.Value { + if (inst.value()) |val| { + return self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = val }); + } + if (self.func_inst_table.get(inst)) |value| return value; + + return self.fail(inst.src, "TODO implement global llvm values (or the value is not in the func_inst_table table)", .{}); + } + + fn genTypedValue(self: *LLVMIRModule, src: usize, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + const llvm_type = try self.getLLVMType(tv.ty, src); + + if (tv.val.isUndef()) + return llvm_type.getUndef(); + + switch (tv.ty.zigTypeTag()) { + .Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(), + .Int => { + var bigint_space: Value.BigIntSpace = undefined; + const bigint = tv.val.toBigInt(&bigint_space); + + if (bigint.eqZero()) return llvm_type.constNull(); + + if (bigint.limbs.len != 1) { + return self.fail(src, "TODO implement bigger bigint", .{}); + } + const llvm_int = llvm_type.constInt(bigint.limbs[0], false); + if (!bigint.positive) { + return llvm.constNeg(llvm_int); + } + return llvm_int; + }, + .Pointer => switch (tv.val.tag()) { + .decl_ref => { + const decl = tv.val.castTag(.decl_ref).?.data; + const val = try self.resolveGlobalDecl(decl, src); + + const usize_type = try self.getLLVMType(Type.initTag(.usize), src); + + // TODO: second index should be the index into the memory! + var indices: [2]*const llvm.Value = .{ + usize_type.constNull(), + usize_type.constNull(), + }; + + // TODO: consider using buildInBoundsGEP2 for opaque pointers + return self.builder.buildInBoundsGEP(val, &indices, 2, ""); + }, + else => return self.fail(src, "TODO implement const of pointer type '{}'", .{tv.ty}), + }, + .Array => { + if (tv.val.castTag(.bytes)) |payload| { + const zero_sentinel = if (tv.ty.sentinel()) |sentinel| blk: { + if (sentinel.tag() == .zero) break :blk true; + return self.fail(src, "TODO handle other sentinel values", .{}); + } else false; + + return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), !zero_sentinel); + } else { + return self.fail(src, "TODO handle more array values", .{}); + } + }, + else => return self.fail(src, "TODO implement const of type '{}'", .{tv.ty}), + } + } + + fn getLLVMType(self: *LLVMIRModule, t: Type, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Type { + switch (t.zigTypeTag()) { + .Void => return self.context.voidType(), + .NoReturn => return self.context.voidType(), + .Int => { + const info = t.intInfo(self.module.getTarget()); + return self.context.intType(info.bits); + }, + .Bool => return self.context.intType(1), + .Pointer => { + if (t.isSlice()) { + return self.fail(src, "TODO: LLVM backend: implement slices", .{}); + } else { + const elem_type = try self.getLLVMType(t.elemType(), src); + return elem_type.pointerType(0); + } + }, + .Array => { + const elem_type = try self.getLLVMType(t.elemType(), src); + return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget()))); + }, + else => return self.fail(src, "TODO implement getLLVMType for type '{}'", .{t}), + } + } + + fn resolveGlobalDecl(self: *LLVMIRModule, decl: *Module.Decl, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + // TODO: do we want to store this in our own datastructure? + if (self.llvm_module.getNamedGlobal(decl.name)) |val| return val; + + const typed_value = decl.typed_value.most_recent.typed_value; + + // TODO: remove this redundant `getLLVMType`, it is also called in `genTypedValue`. + const llvm_type = try self.getLLVMType(typed_value.ty, src); + const val = try self.genTypedValue(src, typed_value); + const global = self.llvm_module.addGlobal(llvm_type, decl.name); + llvm.setInitializer(global, val); + + // TODO ask the Decl if it is const + // https://github.com/ziglang/zig/issues/7582 + + return global; + } + + /// If the llvm function does not exist, create it + fn resolveLLVMFunction(self: *LLVMIRModule, func: *Module.Decl, src: usize) !*const llvm.Value { + // TODO: do we want to store this in our own datastructure? + if (self.llvm_module.getNamedFunction(func.name)) |llvm_fn| return llvm_fn; + + const zig_fn_type = func.typed_value.most_recent.typed_value.ty; + const return_type = zig_fn_type.fnReturnType(); + + const fn_param_len = zig_fn_type.fnParamLen(); + + const fn_param_types = try self.gpa.alloc(Type, fn_param_len); + defer self.gpa.free(fn_param_types); + zig_fn_type.fnParamTypes(fn_param_types); + + const llvm_param = try self.gpa.alloc(*const llvm.Type, fn_param_len); + defer self.gpa.free(llvm_param); + + for (fn_param_types) |fn_param, i| { + llvm_param[i] = try self.getLLVMType(fn_param, src); + } + + const fn_type = llvm.Type.functionType( + try self.getLLVMType(return_type, src), + if (fn_param_len == 0) null else llvm_param.ptr, + @intCast(c_uint, fn_param_len), + false, + ); + const llvm_fn = self.llvm_module.addFunction(func.name, fn_type); + + if (return_type.tag() == .noreturn) { + self.addFnAttr(llvm_fn, "noreturn"); + } + + return llvm_fn; + } + + // Helper functions + fn addAttr(self: LLVMIRModule, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { + const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len); + assert(kind_id != 0); + const llvm_attr = self.context.createEnumAttribute(kind_id, 0); + val.addAttributeAtIndex(index, llvm_attr); + } + + fn addFnAttr(self: *LLVMIRModule, val: *const llvm.Value, attr_name: []const u8) void { + // TODO: improve this API, `addAttr(-1, attr_name)` + self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name); + } + + pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + @setCold(true); + assert(self.err_msg == null); + self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args); + return error.CodegenFail; + } +}; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig new file mode 100644 index 0000000000..cd928877b1 --- /dev/null +++ b/src/codegen/llvm/bindings.zig @@ -0,0 +1,571 @@ +//! We do this instead of @cImport because the self-hosted compiler is easier +//! to bootstrap if it does not depend on translate-c. + +const LLVMBool = bool; +pub const AttributeIndex = c_uint; + +/// Make sure to use the *InContext functions instead of the global ones. +pub const Context = opaque { + pub const create = LLVMContextCreate; + extern fn LLVMContextCreate() *const Context; + + pub const dispose = LLVMContextDispose; + extern fn LLVMContextDispose(C: *const Context) void; + + pub const createEnumAttribute = LLVMCreateEnumAttribute; + extern fn LLVMCreateEnumAttribute(*const Context, KindID: c_uint, Val: u64) *const Attribute; + + pub const intType = LLVMIntTypeInContext; + extern fn LLVMIntTypeInContext(C: *const Context, NumBits: c_uint) *const Type; + + pub const voidType = LLVMVoidTypeInContext; + extern fn LLVMVoidTypeInContext(C: *const Context) *const Type; + + pub const constString = LLVMConstStringInContext; + extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: LLVMBool) *const Value; + + pub const appendBasicBlock = LLVMAppendBasicBlockInContext; + extern fn LLVMAppendBasicBlockInContext(C: *const Context, Fn: *const Value, Name: [*:0]const u8) *const BasicBlock; + + pub const createBuilder = LLVMCreateBuilderInContext; + extern fn LLVMCreateBuilderInContext(C: *const Context) *const Builder; +}; + +pub const Value = opaque { + pub const addAttributeAtIndex = LLVMAddAttributeAtIndex; + extern fn LLVMAddAttributeAtIndex(*const Value, Idx: AttributeIndex, A: *const Attribute) void; + + pub const getFirstBasicBlock = LLVMGetFirstBasicBlock; + extern fn LLVMGetFirstBasicBlock(Fn: *const Value) ?*const BasicBlock; + + pub const getNextInstruction = LLVMGetNextInstruction; + extern fn LLVMGetNextInstruction(Inst: *const Value) ?*const Value; +}; + +pub const Type = opaque { + pub const functionType = LLVMFunctionType; + extern fn LLVMFunctionType(ReturnType: *const Type, ParamTypes: ?[*]*const Type, ParamCount: c_uint, IsVarArg: LLVMBool) *const Type; + + pub const constNull = LLVMConstNull; + extern fn LLVMConstNull(Ty: *const Type) *const Value; + + pub const constAllOnes = LLVMConstAllOnes; + extern fn LLVMConstAllOnes(Ty: *const Type) *const Value; + + pub const constInt = LLVMConstInt; + extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: LLVMBool) *const Value; + + pub const constArray = LLVMConstArray; + extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: ?[*]*const Value, Length: c_uint) *const Value; + + pub const getUndef = LLVMGetUndef; + extern fn LLVMGetUndef(Ty: *const Type) *const Value; + + pub const pointerType = LLVMPointerType; + extern fn LLVMPointerType(ElementType: *const Type, AddressSpace: c_uint) *const Type; + + pub const arrayType = LLVMArrayType; + extern fn LLVMArrayType(ElementType: *const Type, ElementCount: c_uint) *const Type; +}; + +pub const Module = opaque { + pub const createWithName = LLVMModuleCreateWithNameInContext; + extern fn LLVMModuleCreateWithNameInContext(ModuleID: [*:0]const u8, C: *const Context) *const Module; + + pub const dispose = LLVMDisposeModule; + extern fn LLVMDisposeModule(*const Module) void; + + pub const verify = LLVMVerifyModule; + extern fn LLVMVerifyModule(*const Module, Action: VerifierFailureAction, OutMessage: *[*:0]const u8) LLVMBool; + + pub const addFunction = LLVMAddFunction; + extern fn LLVMAddFunction(*const Module, Name: [*:0]const u8, FunctionTy: *const Type) *const Value; + + pub const getNamedFunction = LLVMGetNamedFunction; + extern fn LLVMGetNamedFunction(*const Module, Name: [*:0]const u8) ?*const Value; + + pub const getIntrinsicDeclaration = LLVMGetIntrinsicDeclaration; + extern fn LLVMGetIntrinsicDeclaration(Mod: *const Module, ID: c_uint, ParamTypes: ?[*]*const Type, ParamCount: usize) *const Value; + + pub const printToString = LLVMPrintModuleToString; + extern fn LLVMPrintModuleToString(*const Module) [*:0]const u8; + + pub const addGlobal = LLVMAddGlobal; + extern fn LLVMAddGlobal(M: *const Module, Ty: *const Type, Name: [*:0]const u8) *const Value; + + pub const getNamedGlobal = LLVMGetNamedGlobal; + extern fn LLVMGetNamedGlobal(M: *const Module, Name: [*:0]const u8) ?*const Value; +}; + +pub const lookupIntrinsicID = LLVMLookupIntrinsicID; +extern fn LLVMLookupIntrinsicID(Name: [*]const u8, NameLen: usize) c_uint; + +pub const disposeMessage = LLVMDisposeMessage; +extern fn LLVMDisposeMessage(Message: [*:0]const u8) void; + +pub const VerifierFailureAction = extern enum { + AbortProcess, + PrintMessage, + ReturnStatus, +}; + +pub const constNeg = LLVMConstNeg; +extern fn LLVMConstNeg(ConstantVal: *const Value) *const Value; + +pub const setInitializer = LLVMSetInitializer; +extern fn LLVMSetInitializer(GlobalVar: *const Value, ConstantVal: *const Value) void; + +pub const getParam = LLVMGetParam; +extern fn LLVMGetParam(Fn: *const Value, Index: c_uint) *const Value; + +pub const getEnumAttributeKindForName = LLVMGetEnumAttributeKindForName; +extern fn LLVMGetEnumAttributeKindForName(Name: [*]const u8, SLen: usize) c_uint; + +pub const Attribute = opaque {}; + +pub const Builder = opaque { + pub const dispose = LLVMDisposeBuilder; + extern fn LLVMDisposeBuilder(Builder: *const Builder) void; + + pub const positionBuilder = LLVMPositionBuilder; + extern fn LLVMPositionBuilder(Builder: *const Builder, Block: *const BasicBlock, Instr: *const Value) void; + + pub const positionBuilderAtEnd = LLVMPositionBuilderAtEnd; + extern fn LLVMPositionBuilderAtEnd(Builder: *const Builder, Block: *const BasicBlock) void; + + pub const getInsertBlock = LLVMGetInsertBlock; + extern fn LLVMGetInsertBlock(Builder: *const Builder) *const BasicBlock; + + pub const buildCall = LLVMBuildCall; + extern fn LLVMBuildCall(*const Builder, Fn: *const Value, Args: ?[*]*const Value, NumArgs: c_uint, Name: [*:0]const u8) *const Value; + + pub const buildCall2 = LLVMBuildCall2; + extern fn LLVMBuildCall2(*const Builder, *const Type, Fn: *const Value, Args: [*]*const Value, NumArgs: c_uint, Name: [*:0]const u8) *const Value; + + pub const buildRetVoid = LLVMBuildRetVoid; + extern fn LLVMBuildRetVoid(*const Builder) *const Value; + + pub const buildRet = LLVMBuildRet; + extern fn LLVMBuildRet(*const Builder, V: *const Value) *const Value; + + pub const buildUnreachable = LLVMBuildUnreachable; + extern fn LLVMBuildUnreachable(*const Builder) *const Value; + + pub const buildAlloca = LLVMBuildAlloca; + extern fn LLVMBuildAlloca(*const Builder, Ty: *const Type, Name: [*:0]const u8) *const Value; + + pub const buildStore = LLVMBuildStore; + extern fn LLVMBuildStore(*const Builder, Val: *const Value, Ptr: *const Value) *const Value; + + pub const buildLoad = LLVMBuildLoad; + extern fn LLVMBuildLoad(*const Builder, PointerVal: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildNot = LLVMBuildNot; + extern fn LLVMBuildNot(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildNSWAdd = LLVMBuildNSWAdd; + extern fn LLVMBuildNSWAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildNUWAdd = LLVMBuildNUWAdd; + extern fn LLVMBuildNUWAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildNSWSub = LLVMBuildNSWSub; + extern fn LLVMBuildNSWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildNUWSub = LLVMBuildNUWSub; + extern fn LLVMBuildNUWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildIntCast2 = LLVMBuildIntCast2; + extern fn LLVMBuildIntCast2(*const Builder, Val: *const Value, DestTy: *const Type, IsSigned: LLVMBool, Name: [*:0]const u8) *const Value; + + pub const buildBitCast = LLVMBuildBitCast; + extern fn LLVMBuildBitCast(*const Builder, Val: *const Value, DestTy: *const Type, Name: [*:0]const u8) *const Value; + + pub const buildInBoundsGEP = LLVMBuildInBoundsGEP; + extern fn LLVMBuildInBoundsGEP(B: *const Builder, Pointer: *const Value, Indices: [*]*const Value, NumIndices: c_uint, Name: [*:0]const u8) *const Value; +}; + +pub const BasicBlock = opaque { + pub const deleteBasicBlock = LLVMDeleteBasicBlock; + extern fn LLVMDeleteBasicBlock(BB: *const BasicBlock) void; + + pub const getFirstInstruction = LLVMGetFirstInstruction; + extern fn LLVMGetFirstInstruction(BB: *const BasicBlock) ?*const Value; +}; + +pub const TargetMachine = opaque { + pub const create = LLVMCreateTargetMachine; + extern fn LLVMCreateTargetMachine( + T: *const Target, + Triple: [*:0]const u8, + CPU: [*:0]const u8, + Features: [*:0]const u8, + Level: CodeGenOptLevel, + Reloc: RelocMode, + CodeModel: CodeMode, + ) *const TargetMachine; + + pub const dispose = LLVMDisposeTargetMachine; + extern fn LLVMDisposeTargetMachine(T: *const TargetMachine) void; + + pub const emitToFile = LLVMTargetMachineEmitToFile; + extern fn LLVMTargetMachineEmitToFile(*const TargetMachine, M: *const Module, Filename: [*:0]const u8, codegen: CodeGenFileType, ErrorMessage: *[*:0]const u8) LLVMBool; +}; + +pub const CodeMode = extern enum { + Default, + JITDefault, + Tiny, + Small, + Kernel, + Medium, + Large, +}; + +pub const CodeGenOptLevel = extern enum { + None, + Less, + Default, + Aggressive, +}; + +pub const RelocMode = extern enum { + Default, + Static, + PIC, + DynamicNoPic, + ROPI, + RWPI, + ROPI_RWPI, +}; + +pub const CodeGenFileType = extern enum { + AssemblyFile, + ObjectFile, +}; + +pub const Target = opaque { + pub const getFromTriple = LLVMGetTargetFromTriple; + extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **const Target, ErrorMessage: *[*:0]const u8) LLVMBool; +}; + +extern fn LLVMInitializeAArch64TargetInfo() void; +extern fn LLVMInitializeAMDGPUTargetInfo() void; +extern fn LLVMInitializeARMTargetInfo() void; +extern fn LLVMInitializeAVRTargetInfo() void; +extern fn LLVMInitializeBPFTargetInfo() void; +extern fn LLVMInitializeHexagonTargetInfo() void; +extern fn LLVMInitializeLanaiTargetInfo() void; +extern fn LLVMInitializeMipsTargetInfo() void; +extern fn LLVMInitializeMSP430TargetInfo() void; +extern fn LLVMInitializeNVPTXTargetInfo() void; +extern fn LLVMInitializePowerPCTargetInfo() void; +extern fn LLVMInitializeRISCVTargetInfo() void; +extern fn LLVMInitializeSparcTargetInfo() void; +extern fn LLVMInitializeSystemZTargetInfo() void; +extern fn LLVMInitializeWebAssemblyTargetInfo() void; +extern fn LLVMInitializeX86TargetInfo() void; +extern fn LLVMInitializeXCoreTargetInfo() void; +extern fn LLVMInitializeAArch64Target() void; +extern fn LLVMInitializeAMDGPUTarget() void; +extern fn LLVMInitializeARMTarget() void; +extern fn LLVMInitializeAVRTarget() void; +extern fn LLVMInitializeBPFTarget() void; +extern fn LLVMInitializeHexagonTarget() void; +extern fn LLVMInitializeLanaiTarget() void; +extern fn LLVMInitializeMipsTarget() void; +extern fn LLVMInitializeMSP430Target() void; +extern fn LLVMInitializeNVPTXTarget() void; +extern fn LLVMInitializePowerPCTarget() void; +extern fn LLVMInitializeRISCVTarget() void; +extern fn LLVMInitializeSparcTarget() void; +extern fn LLVMInitializeSystemZTarget() void; +extern fn LLVMInitializeWebAssemblyTarget() void; +extern fn LLVMInitializeX86Target() void; +extern fn LLVMInitializeXCoreTarget() void; +extern fn LLVMInitializeAArch64TargetMC() void; +extern fn LLVMInitializeAMDGPUTargetMC() void; +extern fn LLVMInitializeARMTargetMC() void; +extern fn LLVMInitializeAVRTargetMC() void; +extern fn LLVMInitializeBPFTargetMC() void; +extern fn LLVMInitializeHexagonTargetMC() void; +extern fn LLVMInitializeLanaiTargetMC() void; +extern fn LLVMInitializeMipsTargetMC() void; +extern fn LLVMInitializeMSP430TargetMC() void; +extern fn LLVMInitializeNVPTXTargetMC() void; +extern fn LLVMInitializePowerPCTargetMC() void; +extern fn LLVMInitializeRISCVTargetMC() void; +extern fn LLVMInitializeSparcTargetMC() void; +extern fn LLVMInitializeSystemZTargetMC() void; +extern fn LLVMInitializeWebAssemblyTargetMC() void; +extern fn LLVMInitializeX86TargetMC() void; +extern fn LLVMInitializeXCoreTargetMC() void; +extern fn LLVMInitializeAArch64AsmPrinter() void; +extern fn LLVMInitializeAMDGPUAsmPrinter() void; +extern fn LLVMInitializeARMAsmPrinter() void; +extern fn LLVMInitializeAVRAsmPrinter() void; +extern fn LLVMInitializeBPFAsmPrinter() void; +extern fn LLVMInitializeHexagonAsmPrinter() void; +extern fn LLVMInitializeLanaiAsmPrinter() void; +extern fn LLVMInitializeMipsAsmPrinter() void; +extern fn LLVMInitializeMSP430AsmPrinter() void; +extern fn LLVMInitializeNVPTXAsmPrinter() void; +extern fn LLVMInitializePowerPCAsmPrinter() void; +extern fn LLVMInitializeRISCVAsmPrinter() void; +extern fn LLVMInitializeSparcAsmPrinter() void; +extern fn LLVMInitializeSystemZAsmPrinter() void; +extern fn LLVMInitializeWebAssemblyAsmPrinter() void; +extern fn LLVMInitializeX86AsmPrinter() void; +extern fn LLVMInitializeXCoreAsmPrinter() void; +extern fn LLVMInitializeAArch64AsmParser() void; +extern fn LLVMInitializeAMDGPUAsmParser() void; +extern fn LLVMInitializeARMAsmParser() void; +extern fn LLVMInitializeAVRAsmParser() void; +extern fn LLVMInitializeBPFAsmParser() void; +extern fn LLVMInitializeHexagonAsmParser() void; +extern fn LLVMInitializeLanaiAsmParser() void; +extern fn LLVMInitializeMipsAsmParser() void; +extern fn LLVMInitializeMSP430AsmParser() void; +extern fn LLVMInitializePowerPCAsmParser() void; +extern fn LLVMInitializeRISCVAsmParser() void; +extern fn LLVMInitializeSparcAsmParser() void; +extern fn LLVMInitializeSystemZAsmParser() void; +extern fn LLVMInitializeWebAssemblyAsmParser() void; +extern fn LLVMInitializeX86AsmParser() void; + +pub const initializeAllTargetInfos = LLVMInitializeAllTargetInfos; +fn LLVMInitializeAllTargetInfos() callconv(.C) void { + LLVMInitializeAArch64TargetInfo(); + LLVMInitializeAMDGPUTargetInfo(); + LLVMInitializeARMTargetInfo(); + LLVMInitializeAVRTargetInfo(); + LLVMInitializeBPFTargetInfo(); + LLVMInitializeHexagonTargetInfo(); + LLVMInitializeLanaiTargetInfo(); + LLVMInitializeMipsTargetInfo(); + LLVMInitializeMSP430TargetInfo(); + LLVMInitializeNVPTXTargetInfo(); + LLVMInitializePowerPCTargetInfo(); + LLVMInitializeRISCVTargetInfo(); + LLVMInitializeSparcTargetInfo(); + LLVMInitializeSystemZTargetInfo(); + LLVMInitializeWebAssemblyTargetInfo(); + LLVMInitializeX86TargetInfo(); + LLVMInitializeXCoreTargetInfo(); +} +pub const initializeAllTargets = LLVMInitializeAllTargets; +fn LLVMInitializeAllTargets() callconv(.C) void { + LLVMInitializeAArch64Target(); + LLVMInitializeAMDGPUTarget(); + LLVMInitializeARMTarget(); + LLVMInitializeAVRTarget(); + LLVMInitializeBPFTarget(); + LLVMInitializeHexagonTarget(); + LLVMInitializeLanaiTarget(); + LLVMInitializeMipsTarget(); + LLVMInitializeMSP430Target(); + LLVMInitializeNVPTXTarget(); + LLVMInitializePowerPCTarget(); + LLVMInitializeRISCVTarget(); + LLVMInitializeSparcTarget(); + LLVMInitializeSystemZTarget(); + LLVMInitializeWebAssemblyTarget(); + LLVMInitializeX86Target(); + LLVMInitializeXCoreTarget(); +} +pub const initializeAllTargetMCs = LLVMInitializeAllTargetMCs; +fn LLVMInitializeAllTargetMCs() callconv(.C) void { + LLVMInitializeAArch64TargetMC(); + LLVMInitializeAMDGPUTargetMC(); + LLVMInitializeARMTargetMC(); + LLVMInitializeAVRTargetMC(); + LLVMInitializeBPFTargetMC(); + LLVMInitializeHexagonTargetMC(); + LLVMInitializeLanaiTargetMC(); + LLVMInitializeMipsTargetMC(); + LLVMInitializeMSP430TargetMC(); + LLVMInitializeNVPTXTargetMC(); + LLVMInitializePowerPCTargetMC(); + LLVMInitializeRISCVTargetMC(); + LLVMInitializeSparcTargetMC(); + LLVMInitializeSystemZTargetMC(); + LLVMInitializeWebAssemblyTargetMC(); + LLVMInitializeX86TargetMC(); + LLVMInitializeXCoreTargetMC(); +} +pub const initializeAllAsmPrinters = LLVMInitializeAllAsmPrinters; +fn LLVMInitializeAllAsmPrinters() callconv(.C) void { + LLVMInitializeAArch64AsmPrinter(); + LLVMInitializeAMDGPUAsmPrinter(); + LLVMInitializeARMAsmPrinter(); + LLVMInitializeAVRAsmPrinter(); + LLVMInitializeBPFAsmPrinter(); + LLVMInitializeHexagonAsmPrinter(); + LLVMInitializeLanaiAsmPrinter(); + LLVMInitializeMipsAsmPrinter(); + LLVMInitializeMSP430AsmPrinter(); + LLVMInitializeNVPTXAsmPrinter(); + LLVMInitializePowerPCAsmPrinter(); + LLVMInitializeRISCVAsmPrinter(); + LLVMInitializeSparcAsmPrinter(); + LLVMInitializeSystemZAsmPrinter(); + LLVMInitializeWebAssemblyAsmPrinter(); + LLVMInitializeX86AsmPrinter(); + LLVMInitializeXCoreAsmPrinter(); +} +pub const initializeAllAsmParsers = LLVMInitializeAllAsmParsers; +fn LLVMInitializeAllAsmParsers() callconv(.C) void { + LLVMInitializeAArch64AsmParser(); + LLVMInitializeAMDGPUAsmParser(); + LLVMInitializeARMAsmParser(); + LLVMInitializeAVRAsmParser(); + LLVMInitializeBPFAsmParser(); + LLVMInitializeHexagonAsmParser(); + LLVMInitializeLanaiAsmParser(); + LLVMInitializeMipsAsmParser(); + LLVMInitializeMSP430AsmParser(); + LLVMInitializePowerPCAsmParser(); + LLVMInitializeRISCVAsmParser(); + LLVMInitializeSparcAsmParser(); + LLVMInitializeSystemZAsmParser(); + LLVMInitializeWebAssemblyAsmParser(); + LLVMInitializeX86AsmParser(); +} + +extern fn ZigLLDLinkCOFF(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; +extern fn ZigLLDLinkELF(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; +extern fn ZigLLDLinkMachO(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; +extern fn ZigLLDLinkWasm(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; + +pub const LinkCOFF = ZigLLDLinkCOFF; +pub const LinkELF = ZigLLDLinkELF; +pub const LinkMachO = ZigLLDLinkMachO; +pub const LinkWasm = ZigLLDLinkWasm; + +pub const ObjectFormatType = extern enum(c_int) { + Unknown, + COFF, + ELF, + MachO, + Wasm, + XCOFF, +}; + +pub const GetHostCPUName = LLVMGetHostCPUName; +extern fn LLVMGetHostCPUName() ?[*:0]u8; + +pub const GetNativeFeatures = ZigLLVMGetNativeFeatures; +extern fn ZigLLVMGetNativeFeatures() ?[*:0]u8; + +pub const WriteArchive = ZigLLVMWriteArchive; +extern fn ZigLLVMWriteArchive( + archive_name: [*:0]const u8, + file_names_ptr: [*]const [*:0]const u8, + file_names_len: usize, + os_type: OSType, +) bool; + +pub const OSType = extern enum(c_int) { + UnknownOS = 0, + Ananas = 1, + CloudABI = 2, + Darwin = 3, + DragonFly = 4, + FreeBSD = 5, + Fuchsia = 6, + IOS = 7, + KFreeBSD = 8, + Linux = 9, + Lv2 = 10, + MacOSX = 11, + NetBSD = 12, + OpenBSD = 13, + Solaris = 14, + Win32 = 15, + Haiku = 16, + Minix = 17, + RTEMS = 18, + NaCl = 19, + CNK = 20, + AIX = 21, + CUDA = 22, + NVCL = 23, + AMDHSA = 24, + PS4 = 25, + ELFIAMCU = 26, + TvOS = 27, + WatchOS = 28, + Mesa3D = 29, + Contiki = 30, + AMDPAL = 31, + HermitCore = 32, + Hurd = 33, + WASI = 34, + Emscripten = 35, +}; + +pub const ArchType = extern enum(c_int) { + UnknownArch = 0, + arm = 1, + armeb = 2, + aarch64 = 3, + aarch64_be = 4, + aarch64_32 = 5, + arc = 6, + avr = 7, + bpfel = 8, + bpfeb = 9, + hexagon = 10, + mips = 11, + mipsel = 12, + mips64 = 13, + mips64el = 14, + msp430 = 15, + ppc = 16, + ppc64 = 17, + ppc64le = 18, + r600 = 19, + amdgcn = 20, + riscv32 = 21, + riscv64 = 22, + sparc = 23, + sparcv9 = 24, + sparcel = 25, + systemz = 26, + tce = 27, + tcele = 28, + thumb = 29, + thumbeb = 30, + x86 = 31, + x86_64 = 32, + xcore = 33, + nvptx = 34, + nvptx64 = 35, + le32 = 36, + le64 = 37, + amdil = 38, + amdil64 = 39, + hsail = 40, + hsail64 = 41, + spir = 42, + spir64 = 43, + kalimba = 44, + shave = 45, + lanai = 46, + wasm32 = 47, + wasm64 = 48, + renderscript32 = 49, + renderscript64 = 50, + ve = 51, +}; + +pub const ParseCommandLineOptions = ZigLLVMParseCommandLineOptions; +extern fn ZigLLVMParseCommandLineOptions(argc: usize, argv: [*]const [*:0]const u8) void; + +pub const WriteImportLibrary = ZigLLVMWriteImportLibrary; +extern fn ZigLLVMWriteImportLibrary( + def_path: [*:0]const u8, + arch: ArchType, + output_lib_path: [*c]const u8, + kill_at: bool, +) bool; diff --git a/src/link.zig b/src/link.zig index 3dbfb3b922..7893e69cd5 100644 --- a/src/link.zig +++ b/src/link.zig @@ -567,7 +567,7 @@ pub const File = struct { std.debug.print("\n", .{}); } - const llvm = @import("llvm_bindings.zig"); + const llvm = @import("codegen/llvm/bindings.zig"); const os_type = @import("target.zig").osToLLVM(base.options.target.os.tag); const bad = llvm.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_type); if (bad) return error.UnableToWriteArchive; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 117bdef4a7..f7d646356c 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -16,7 +16,7 @@ const link = @import("../link.zig"); const build_options = @import("build_options"); const Cache = @import("../Cache.zig"); const mingw = @import("../mingw.zig"); -const llvm_backend = @import("../llvm_backend.zig"); +const llvm_backend = @import("../codegen/llvm.zig"); const allocation_padding = 4 / 3; const minimum_text_block_size = 64 * allocation_padding; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index b6b8fd7750..f3073824a5 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -24,7 +24,7 @@ const build_options = @import("build_options"); const target_util = @import("../target.zig"); const glibc = @import("../glibc.zig"); const Cache = @import("../Cache.zig"); -const llvm_backend = @import("../llvm_backend.zig"); +const llvm_backend = @import("../codegen/llvm.zig"); const default_entry_addr = 0x8000000; diff --git a/src/llvm_backend.zig b/src/llvm_backend.zig deleted file mode 100644 index 2bac4523c9..0000000000 --- a/src/llvm_backend.zig +++ /dev/null @@ -1,722 +0,0 @@ -const std = @import("std"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; -const Compilation = @import("Compilation.zig"); -const llvm = @import("llvm_bindings.zig"); -const link = @import("link.zig"); -const log = std.log.scoped(.codegen); - -const Module = @import("Module.zig"); -const TypedValue = @import("TypedValue.zig"); -const ir = @import("ir.zig"); -const Inst = ir.Inst; - -const Value = @import("value.zig").Value; -const Type = @import("type.zig").Type; - -pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { - const llvm_arch = switch (target.cpu.arch) { - .arm => "arm", - .armeb => "armeb", - .aarch64 => "aarch64", - .aarch64_be => "aarch64_be", - .aarch64_32 => "aarch64_32", - .arc => "arc", - .avr => "avr", - .bpfel => "bpfel", - .bpfeb => "bpfeb", - .hexagon => "hexagon", - .mips => "mips", - .mipsel => "mipsel", - .mips64 => "mips64", - .mips64el => "mips64el", - .msp430 => "msp430", - .powerpc => "powerpc", - .powerpc64 => "powerpc64", - .powerpc64le => "powerpc64le", - .r600 => "r600", - .amdgcn => "amdgcn", - .riscv32 => "riscv32", - .riscv64 => "riscv64", - .sparc => "sparc", - .sparcv9 => "sparcv9", - .sparcel => "sparcel", - .s390x => "s390x", - .tce => "tce", - .tcele => "tcele", - .thumb => "thumb", - .thumbeb => "thumbeb", - .i386 => "i386", - .x86_64 => "x86_64", - .xcore => "xcore", - .nvptx => "nvptx", - .nvptx64 => "nvptx64", - .le32 => "le32", - .le64 => "le64", - .amdil => "amdil", - .amdil64 => "amdil64", - .hsail => "hsail", - .hsail64 => "hsail64", - .spir => "spir", - .spir64 => "spir64", - .kalimba => "kalimba", - .shave => "shave", - .lanai => "lanai", - .wasm32 => "wasm32", - .wasm64 => "wasm64", - .renderscript32 => "renderscript32", - .renderscript64 => "renderscript64", - .ve => "ve", - .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, - }; - // TODO Add a sub-arch for some architectures depending on CPU features. - - const llvm_os = switch (target.os.tag) { - .freestanding => "unknown", - .ananas => "ananas", - .cloudabi => "cloudabi", - .dragonfly => "dragonfly", - .freebsd => "freebsd", - .fuchsia => "fuchsia", - .ios => "ios", - .kfreebsd => "kfreebsd", - .linux => "linux", - .lv2 => "lv2", - .macos => "macosx", - .netbsd => "netbsd", - .openbsd => "openbsd", - .solaris => "solaris", - .windows => "windows", - .haiku => "haiku", - .minix => "minix", - .rtems => "rtems", - .nacl => "nacl", - .cnk => "cnk", - .aix => "aix", - .cuda => "cuda", - .nvcl => "nvcl", - .amdhsa => "amdhsa", - .ps4 => "ps4", - .elfiamcu => "elfiamcu", - .tvos => "tvos", - .watchos => "watchos", - .mesa3d => "mesa3d", - .contiki => "contiki", - .amdpal => "amdpal", - .hermit => "hermit", - .hurd => "hurd", - .wasi => "wasi", - .emscripten => "emscripten", - .uefi => "windows", - .other => "unknown", - }; - - const llvm_abi = switch (target.abi) { - .none => "unknown", - .gnu => "gnu", - .gnuabin32 => "gnuabin32", - .gnuabi64 => "gnuabi64", - .gnueabi => "gnueabi", - .gnueabihf => "gnueabihf", - .gnux32 => "gnux32", - .code16 => "code16", - .eabi => "eabi", - .eabihf => "eabihf", - .android => "android", - .musl => "musl", - .musleabi => "musleabi", - .musleabihf => "musleabihf", - .msvc => "msvc", - .itanium => "itanium", - .cygnus => "cygnus", - .coreclr => "coreclr", - .simulator => "simulator", - .macabi => "macabi", - }; - - return std.fmt.allocPrintZ(allocator, "{s}-unknown-{s}-{s}", .{ llvm_arch, llvm_os, llvm_abi }); -} - -pub const LLVMIRModule = struct { - module: *Module, - llvm_module: *const llvm.Module, - context: *const llvm.Context, - target_machine: *const llvm.TargetMachine, - builder: *const llvm.Builder, - - object_path: []const u8, - - gpa: *Allocator, - err_msg: ?*Compilation.ErrorMsg = null, - - // TODO: The fields below should really move into a different struct, - // because they are only valid when generating a function - - /// This stores the LLVM values used in a function, such that they can be - /// referred to in other instructions. This table is cleared before every function is generated. - func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value) = .{}, - - /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction. - args: []*const llvm.Value = &[_]*const llvm.Value{}, - arg_index: usize = 0, - - entry_block: *const llvm.BasicBlock = undefined, - /// This fields stores the last alloca instruction, such that we can append more alloca instructions - /// to the top of the function. - latest_alloca_inst: ?*const llvm.Value = null, - - pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*LLVMIRModule { - const self = try allocator.create(LLVMIRModule); - errdefer allocator.destroy(self); - - const gpa = options.module.?.gpa; - - const obj_basename = try std.zig.binNameAlloc(gpa, .{ - .root_name = options.root_name, - .target = options.target, - .output_mode = .Obj, - }); - defer gpa.free(obj_basename); - - const o_directory = options.module.?.zig_cache_artifact_directory; - const object_path = try o_directory.join(gpa, &[_][]const u8{obj_basename}); - errdefer gpa.free(object_path); - - const context = llvm.Context.create(); - errdefer context.dispose(); - - initializeLLVMTargets(); - - const root_nameZ = try gpa.dupeZ(u8, options.root_name); - defer gpa.free(root_nameZ); - const llvm_module = llvm.Module.createWithName(root_nameZ.ptr, context); - errdefer llvm_module.dispose(); - - const llvm_target_triple = try targetTriple(gpa, options.target); - defer gpa.free(llvm_target_triple); - - var error_message: [*:0]const u8 = undefined; - var target: *const llvm.Target = undefined; - if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message)) { - defer llvm.disposeMessage(error_message); - - const stderr = std.io.getStdErr().outStream(); - try stderr.print( - \\Zig is expecting LLVM to understand this target: '{s}' - \\However LLVM responded with: "{s}" - \\Zig is unable to continue. This is a bug in Zig: - \\https://github.com/ziglang/zig/issues/438 - \\ - , - .{ - llvm_target_triple, - error_message, - }, - ); - return error.InvalidLLVMTriple; - } - - const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug) .None else .Aggressive; - const target_machine = llvm.TargetMachine.create( - target, - llvm_target_triple.ptr, - "", - "", - opt_level, - .Static, - .Default, - ); - errdefer target_machine.dispose(); - - const builder = context.createBuilder(); - errdefer builder.dispose(); - - self.* = .{ - .module = options.module.?, - .llvm_module = llvm_module, - .context = context, - .target_machine = target_machine, - .builder = builder, - .object_path = object_path, - .gpa = gpa, - }; - return self; - } - - pub fn deinit(self: *LLVMIRModule, allocator: *Allocator) void { - self.builder.dispose(); - self.target_machine.dispose(); - self.llvm_module.dispose(); - self.context.dispose(); - - self.func_inst_table.deinit(self.gpa); - self.gpa.free(self.object_path); - - allocator.destroy(self); - } - - fn initializeLLVMTargets() void { - llvm.initializeAllTargets(); - llvm.initializeAllTargetInfos(); - llvm.initializeAllTargetMCs(); - llvm.initializeAllAsmPrinters(); - llvm.initializeAllAsmParsers(); - } - - pub fn flushModule(self: *LLVMIRModule, comp: *Compilation) !void { - if (comp.verbose_llvm_ir) { - const dump = self.llvm_module.printToString(); - defer llvm.disposeMessage(dump); - - const stderr = std.io.getStdErr().outStream(); - try stderr.writeAll(std.mem.spanZ(dump)); - } - - { - var error_message: [*:0]const u8 = undefined; - // verifyModule always allocs the error_message even if there is no error - defer llvm.disposeMessage(error_message); - - if (self.llvm_module.verify(.ReturnStatus, &error_message)) { - const stderr = std.io.getStdErr().outStream(); - try stderr.print("broken LLVM module found: {s}\nThis is a bug in the Zig compiler.", .{error_message}); - return error.BrokenLLVMModule; - } - } - - const object_pathZ = try self.gpa.dupeZ(u8, self.object_path); - defer self.gpa.free(object_pathZ); - - var error_message: [*:0]const u8 = undefined; - if (self.target_machine.emitToFile( - self.llvm_module, - object_pathZ.ptr, - .ObjectFile, - &error_message, - )) { - defer llvm.disposeMessage(error_message); - - const stderr = std.io.getStdErr().outStream(); - try stderr.print("LLVM failed to emit file: {s}\n", .{error_message}); - return error.FailedToEmit; - } - } - - pub fn updateDecl(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { - self.gen(module, decl) catch |err| switch (err) { - error.CodegenFail => { - decl.analysis = .codegen_failure; - try module.failed_decls.put(module.gpa, decl, self.err_msg.?); - self.err_msg = null; - return; - }, - else => |e| return e, - }; - } - - fn gen(self: *LLVMIRModule, module: *Module, decl: *Module.Decl) !void { - const typed_value = decl.typed_value.most_recent.typed_value; - const src = decl.src(); - - log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val }); - - if (typed_value.val.castTag(.function)) |func_payload| { - const func = func_payload.data; - - const llvm_func = try self.resolveLLVMFunction(func.owner_decl, src); - - // This gets the LLVM values from the function and stores them in `self.args`. - const fn_param_len = func.owner_decl.typed_value.most_recent.typed_value.ty.fnParamLen(); - var args = try self.gpa.alloc(*const llvm.Value, fn_param_len); - defer self.gpa.free(args); - - for (args) |*arg, i| { - arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i)); - } - self.args = args; - self.arg_index = 0; - - // Make sure no other LLVM values from other functions can be referenced - self.func_inst_table.clearRetainingCapacity(); - - // We remove all the basic blocks of a function to support incremental - // compilation! - // TODO: remove all basic blocks if functions can have more than one - if (llvm_func.getFirstBasicBlock()) |bb| { - bb.deleteBasicBlock(); - } - - self.entry_block = self.context.appendBasicBlock(llvm_func, "Entry"); - self.builder.positionBuilderAtEnd(self.entry_block); - self.latest_alloca_inst = null; - - const instructions = func.body.instructions; - for (instructions) |inst| { - const opt_llvm_val: ?*const llvm.Value = switch (inst.tag) { - .add => try self.genAdd(inst.castTag(.add).?), - .alloc => try self.genAlloc(inst.castTag(.alloc).?), - .arg => try self.genArg(inst.castTag(.arg).?), - .bitcast => try self.genBitCast(inst.castTag(.bitcast).?), - .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?), - .call => try self.genCall(inst.castTag(.call).?), - .intcast => try self.genIntCast(inst.castTag(.intcast).?), - .load => try self.genLoad(inst.castTag(.load).?), - .not => try self.genNot(inst.castTag(.not).?), - .ret => try self.genRet(inst.castTag(.ret).?), - .retvoid => self.genRetVoid(inst.castTag(.retvoid).?), - .store => try self.genStore(inst.castTag(.store).?), - .sub => try self.genSub(inst.castTag(.sub).?), - .unreach => self.genUnreach(inst.castTag(.unreach).?), - .dbg_stmt => blk: { - // TODO: implement debug info - break :blk null; - }, - else => |tag| return self.fail(src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}), - }; - if (opt_llvm_val) |llvm_val| try self.func_inst_table.putNoClobber(self.gpa, inst, llvm_val); - } - } else if (typed_value.val.castTag(.extern_fn)) |extern_fn| { - _ = try self.resolveLLVMFunction(extern_fn.data, src); - } else { - _ = try self.resolveGlobalDecl(decl, src); - } - } - - fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !?*const llvm.Value { - if (inst.func.value()) |func_value| { - const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn| - extern_fn.data - else if (func_value.castTag(.function)) |func_payload| - func_payload.data.owner_decl - else - unreachable; - - const zig_fn_type = fn_decl.typed_value.most_recent.typed_value.ty; - const llvm_fn = try self.resolveLLVMFunction(fn_decl, inst.base.src); - - const num_args = inst.args.len; - - const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, num_args); - defer self.gpa.free(llvm_param_vals); - - for (inst.args) |arg, i| { - llvm_param_vals[i] = try self.resolveInst(arg); - } - - // TODO: LLVMBuildCall2 handles opaque function pointers, according to llvm docs - // Do we need that? - const call = self.builder.buildCall( - llvm_fn, - if (num_args == 0) null else llvm_param_vals.ptr, - @intCast(c_uint, num_args), - "", - ); - - const return_type = zig_fn_type.fnReturnType(); - if (return_type.tag() == .noreturn) { - _ = self.builder.buildUnreachable(); - } - - // No need to store the LLVM value if the return type is void or noreturn - if (!return_type.hasCodeGenBits()) return null; - - return call; - } else { - return self.fail(inst.base.src, "TODO implement calling runtime known function pointer LLVM backend", .{}); - } - } - - fn genRetVoid(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value { - _ = self.builder.buildRetVoid(); - return null; - } - - fn genRet(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { - _ = self.builder.buildRet(try self.resolveInst(inst.operand)); - return null; - } - - fn genNot(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { - return self.builder.buildNot(try self.resolveInst(inst.operand), ""); - } - - fn genUnreach(self: *LLVMIRModule, inst: *Inst.NoOp) ?*const llvm.Value { - _ = self.builder.buildUnreachable(); - return null; - } - - fn genAdd(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { - const lhs = try self.resolveInst(inst.lhs); - const rhs = try self.resolveInst(inst.rhs); - - if (!inst.base.ty.isInt()) - return self.fail(inst.base.src, "TODO implement 'genAdd' for type {}", .{inst.base.ty}); - - return if (inst.base.ty.isSignedInt()) - self.builder.buildNSWAdd(lhs, rhs, "") - else - self.builder.buildNUWAdd(lhs, rhs, ""); - } - - fn genSub(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { - const lhs = try self.resolveInst(inst.lhs); - const rhs = try self.resolveInst(inst.rhs); - - if (!inst.base.ty.isInt()) - return self.fail(inst.base.src, "TODO implement 'genSub' for type {}", .{inst.base.ty}); - - return if (inst.base.ty.isSignedInt()) - self.builder.buildNSWSub(lhs, rhs, "") - else - self.builder.buildNUWSub(lhs, rhs, ""); - } - - fn genIntCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { - const val = try self.resolveInst(inst.operand); - - const signed = inst.base.ty.isSignedInt(); - // TODO: Should we use intcast here or just a simple bitcast? - // LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes - return self.builder.buildIntCast2(val, try self.getLLVMType(inst.base.ty, inst.base.src), signed, ""); - } - - fn genBitCast(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { - const val = try self.resolveInst(inst.operand); - const dest_type = try self.getLLVMType(inst.base.ty, inst.base.src); - - return self.builder.buildBitCast(val, dest_type, ""); - } - - fn genArg(self: *LLVMIRModule, inst: *Inst.Arg) !?*const llvm.Value { - const arg_val = self.args[self.arg_index]; - self.arg_index += 1; - - const ptr_val = self.buildAlloca(try self.getLLVMType(inst.base.ty, inst.base.src)); - _ = self.builder.buildStore(arg_val, ptr_val); - return self.builder.buildLoad(ptr_val, ""); - } - - fn genAlloc(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.Value { - // buildAlloca expects the pointee type, not the pointer type, so assert that - // a Payload.PointerSimple is passed to the alloc instruction. - const pointee_type = inst.base.ty.castPointer().?.data; - - // TODO: figure out a way to get the name of the var decl. - // TODO: set alignment and volatile - return self.buildAlloca(try self.getLLVMType(pointee_type, inst.base.src)); - } - - /// Use this instead of builder.buildAlloca, because this function makes sure to - /// put the alloca instruction at the top of the function! - fn buildAlloca(self: *LLVMIRModule, t: *const llvm.Type) *const llvm.Value { - if (self.latest_alloca_inst) |latest_alloc| { - // builder.positionBuilder adds it before the instruction, - // but we want to put it after the last alloca instruction. - self.builder.positionBuilder(self.entry_block, latest_alloc.getNextInstruction().?); - } else { - // There might have been other instructions emitted before the - // first alloca has been generated. However the alloca should still - // be first in the function. - if (self.entry_block.getFirstInstruction()) |first_inst| { - self.builder.positionBuilder(self.entry_block, first_inst); - } - } - defer self.builder.positionBuilderAtEnd(self.entry_block); - - const val = self.builder.buildAlloca(t, ""); - self.latest_alloca_inst = val; - return val; - } - - fn genStore(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value { - const val = try self.resolveInst(inst.rhs); - const ptr = try self.resolveInst(inst.lhs); - _ = self.builder.buildStore(val, ptr); - return null; - } - - fn genLoad(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { - const ptr_val = try self.resolveInst(inst.operand); - return self.builder.buildLoad(ptr_val, ""); - } - - fn genBreakpoint(self: *LLVMIRModule, inst: *Inst.NoOp) !?*const llvm.Value { - const llvn_fn = self.getIntrinsic("llvm.debugtrap"); - _ = self.builder.buildCall(llvn_fn, null, 0, ""); - return null; - } - - fn getIntrinsic(self: *LLVMIRModule, name: []const u8) *const llvm.Value { - const id = llvm.lookupIntrinsicID(name.ptr, name.len); - assert(id != 0); - // TODO: add support for overload intrinsics by passing the prefix of the intrinsic - // to `lookupIntrinsicID` and then passing the correct types to - // `getIntrinsicDeclaration` - return self.llvm_module.getIntrinsicDeclaration(id, null, 0); - } - - fn resolveInst(self: *LLVMIRModule, inst: *ir.Inst) !*const llvm.Value { - if (inst.value()) |val| { - return self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = val }); - } - if (self.func_inst_table.get(inst)) |value| return value; - - return self.fail(inst.src, "TODO implement global llvm values (or the value is not in the func_inst_table table)", .{}); - } - - fn genTypedValue(self: *LLVMIRModule, src: usize, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - const llvm_type = try self.getLLVMType(tv.ty, src); - - if (tv.val.isUndef()) - return llvm_type.getUndef(); - - switch (tv.ty.zigTypeTag()) { - .Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(), - .Int => { - var bigint_space: Value.BigIntSpace = undefined; - const bigint = tv.val.toBigInt(&bigint_space); - - if (bigint.eqZero()) return llvm_type.constNull(); - - if (bigint.limbs.len != 1) { - return self.fail(src, "TODO implement bigger bigint", .{}); - } - const llvm_int = llvm_type.constInt(bigint.limbs[0], false); - if (!bigint.positive) { - return llvm.constNeg(llvm_int); - } - return llvm_int; - }, - .Pointer => switch (tv.val.tag()) { - .decl_ref => { - const decl = tv.val.castTag(.decl_ref).?.data; - const val = try self.resolveGlobalDecl(decl, src); - - const usize_type = try self.getLLVMType(Type.initTag(.usize), src); - - // TODO: second index should be the index into the memory! - var indices: [2]*const llvm.Value = .{ - usize_type.constNull(), - usize_type.constNull(), - }; - - // TODO: consider using buildInBoundsGEP2 for opaque pointers - return self.builder.buildInBoundsGEP(val, &indices, 2, ""); - }, - else => return self.fail(src, "TODO implement const of pointer type '{}'", .{tv.ty}), - }, - .Array => { - if (tv.val.castTag(.bytes)) |payload| { - const zero_sentinel = if (tv.ty.sentinel()) |sentinel| blk: { - if (sentinel.tag() == .zero) break :blk true; - return self.fail(src, "TODO handle other sentinel values", .{}); - } else false; - - return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), !zero_sentinel); - } else { - return self.fail(src, "TODO handle more array values", .{}); - } - }, - else => return self.fail(src, "TODO implement const of type '{}'", .{tv.ty}), - } - } - - fn getLLVMType(self: *LLVMIRModule, t: Type, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Type { - switch (t.zigTypeTag()) { - .Void => return self.context.voidType(), - .NoReturn => return self.context.voidType(), - .Int => { - const info = t.intInfo(self.module.getTarget()); - return self.context.intType(info.bits); - }, - .Bool => return self.context.intType(1), - .Pointer => { - if (t.isSlice()) { - return self.fail(src, "TODO: LLVM backend: implement slices", .{}); - } else { - const elem_type = try self.getLLVMType(t.elemType(), src); - return elem_type.pointerType(0); - } - }, - .Array => { - const elem_type = try self.getLLVMType(t.elemType(), src); - return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget()))); - }, - else => return self.fail(src, "TODO implement getLLVMType for type '{}'", .{t}), - } - } - - fn resolveGlobalDecl(self: *LLVMIRModule, decl: *Module.Decl, src: usize) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - // TODO: do we want to store this in our own datastructure? - if (self.llvm_module.getNamedGlobal(decl.name)) |val| return val; - - const typed_value = decl.typed_value.most_recent.typed_value; - - // TODO: remove this redundant `getLLVMType`, it is also called in `genTypedValue`. - const llvm_type = try self.getLLVMType(typed_value.ty, src); - const val = try self.genTypedValue(src, typed_value); - const global = self.llvm_module.addGlobal(llvm_type, decl.name); - llvm.setInitializer(global, val); - - // TODO ask the Decl if it is const - // https://github.com/ziglang/zig/issues/7582 - - return global; - } - - /// If the llvm function does not exist, create it - fn resolveLLVMFunction(self: *LLVMIRModule, func: *Module.Decl, src: usize) !*const llvm.Value { - // TODO: do we want to store this in our own datastructure? - if (self.llvm_module.getNamedFunction(func.name)) |llvm_fn| return llvm_fn; - - const zig_fn_type = func.typed_value.most_recent.typed_value.ty; - const return_type = zig_fn_type.fnReturnType(); - - const fn_param_len = zig_fn_type.fnParamLen(); - - const fn_param_types = try self.gpa.alloc(Type, fn_param_len); - defer self.gpa.free(fn_param_types); - zig_fn_type.fnParamTypes(fn_param_types); - - const llvm_param = try self.gpa.alloc(*const llvm.Type, fn_param_len); - defer self.gpa.free(llvm_param); - - for (fn_param_types) |fn_param, i| { - llvm_param[i] = try self.getLLVMType(fn_param, src); - } - - const fn_type = llvm.Type.functionType( - try self.getLLVMType(return_type, src), - if (fn_param_len == 0) null else llvm_param.ptr, - @intCast(c_uint, fn_param_len), - false, - ); - const llvm_fn = self.llvm_module.addFunction(func.name, fn_type); - - if (return_type.tag() == .noreturn) { - self.addFnAttr(llvm_fn, "noreturn"); - } - - return llvm_fn; - } - - // Helper functions - fn addAttr(self: LLVMIRModule, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { - const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len); - assert(kind_id != 0); - const llvm_attr = self.context.createEnumAttribute(kind_id, 0); - val.addAttributeAtIndex(index, llvm_attr); - } - - fn addFnAttr(self: *LLVMIRModule, val: *const llvm.Value, attr_name: []const u8) void { - // TODO: improve this API, `addAttr(-1, attr_name)` - self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name); - } - - pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { - @setCold(true); - assert(self.err_msg == null); - self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args); - return error.CodegenFail; - } -}; diff --git a/src/llvm_bindings.zig b/src/llvm_bindings.zig deleted file mode 100644 index 1af64e85f6..0000000000 --- a/src/llvm_bindings.zig +++ /dev/null @@ -1,574 +0,0 @@ -//! We do this instead of @cImport because the self-hosted compiler is easier -//! to bootstrap if it does not depend on translate-c. - -const std = @import("std"); -const assert = std.debug.assert; - -const LLVMBool = bool; -pub const AttributeIndex = c_uint; - -/// Make sure to use the *InContext functions instead of the global ones. -pub const Context = opaque { - pub const create = LLVMContextCreate; - extern fn LLVMContextCreate() *const Context; - - pub const dispose = LLVMContextDispose; - extern fn LLVMContextDispose(C: *const Context) void; - - pub const createEnumAttribute = LLVMCreateEnumAttribute; - extern fn LLVMCreateEnumAttribute(*const Context, KindID: c_uint, Val: u64) *const Attribute; - - pub const intType = LLVMIntTypeInContext; - extern fn LLVMIntTypeInContext(C: *const Context, NumBits: c_uint) *const Type; - - pub const voidType = LLVMVoidTypeInContext; - extern fn LLVMVoidTypeInContext(C: *const Context) *const Type; - - pub const constString = LLVMConstStringInContext; - extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: LLVMBool) *const Value; - - pub const appendBasicBlock = LLVMAppendBasicBlockInContext; - extern fn LLVMAppendBasicBlockInContext(C: *const Context, Fn: *const Value, Name: [*:0]const u8) *const BasicBlock; - - pub const createBuilder = LLVMCreateBuilderInContext; - extern fn LLVMCreateBuilderInContext(C: *const Context) *const Builder; -}; - -pub const Value = opaque { - pub const addAttributeAtIndex = LLVMAddAttributeAtIndex; - extern fn LLVMAddAttributeAtIndex(*const Value, Idx: AttributeIndex, A: *const Attribute) void; - - pub const getFirstBasicBlock = LLVMGetFirstBasicBlock; - extern fn LLVMGetFirstBasicBlock(Fn: *const Value) ?*const BasicBlock; - - pub const getNextInstruction = LLVMGetNextInstruction; - extern fn LLVMGetNextInstruction(Inst: *const Value) ?*const Value; -}; - -pub const Type = opaque { - pub const functionType = LLVMFunctionType; - extern fn LLVMFunctionType(ReturnType: *const Type, ParamTypes: ?[*]*const Type, ParamCount: c_uint, IsVarArg: LLVMBool) *const Type; - - pub const constNull = LLVMConstNull; - extern fn LLVMConstNull(Ty: *const Type) *const Value; - - pub const constAllOnes = LLVMConstAllOnes; - extern fn LLVMConstAllOnes(Ty: *const Type) *const Value; - - pub const constInt = LLVMConstInt; - extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: LLVMBool) *const Value; - - pub const constArray = LLVMConstArray; - extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: ?[*]*const Value, Length: c_uint) *const Value; - - pub const getUndef = LLVMGetUndef; - extern fn LLVMGetUndef(Ty: *const Type) *const Value; - - pub const pointerType = LLVMPointerType; - extern fn LLVMPointerType(ElementType: *const Type, AddressSpace: c_uint) *const Type; - - pub const arrayType = LLVMArrayType; - extern fn LLVMArrayType(ElementType: *const Type, ElementCount: c_uint) *const Type; -}; - -pub const Module = opaque { - pub const createWithName = LLVMModuleCreateWithNameInContext; - extern fn LLVMModuleCreateWithNameInContext(ModuleID: [*:0]const u8, C: *const Context) *const Module; - - pub const dispose = LLVMDisposeModule; - extern fn LLVMDisposeModule(*const Module) void; - - pub const verify = LLVMVerifyModule; - extern fn LLVMVerifyModule(*const Module, Action: VerifierFailureAction, OutMessage: *[*:0]const u8) LLVMBool; - - pub const addFunction = LLVMAddFunction; - extern fn LLVMAddFunction(*const Module, Name: [*:0]const u8, FunctionTy: *const Type) *const Value; - - pub const getNamedFunction = LLVMGetNamedFunction; - extern fn LLVMGetNamedFunction(*const Module, Name: [*:0]const u8) ?*const Value; - - pub const getIntrinsicDeclaration = LLVMGetIntrinsicDeclaration; - extern fn LLVMGetIntrinsicDeclaration(Mod: *const Module, ID: c_uint, ParamTypes: ?[*]*const Type, ParamCount: usize) *const Value; - - pub const printToString = LLVMPrintModuleToString; - extern fn LLVMPrintModuleToString(*const Module) [*:0]const u8; - - pub const addGlobal = LLVMAddGlobal; - extern fn LLVMAddGlobal(M: *const Module, Ty: *const Type, Name: [*:0]const u8) *const Value; - - pub const getNamedGlobal = LLVMGetNamedGlobal; - extern fn LLVMGetNamedGlobal(M: *const Module, Name: [*:0]const u8) ?*const Value; -}; - -pub const lookupIntrinsicID = LLVMLookupIntrinsicID; -extern fn LLVMLookupIntrinsicID(Name: [*]const u8, NameLen: usize) c_uint; - -pub const disposeMessage = LLVMDisposeMessage; -extern fn LLVMDisposeMessage(Message: [*:0]const u8) void; - -pub const VerifierFailureAction = extern enum { - AbortProcess, - PrintMessage, - ReturnStatus, -}; - -pub const constNeg = LLVMConstNeg; -extern fn LLVMConstNeg(ConstantVal: *const Value) *const Value; - -pub const setInitializer = LLVMSetInitializer; -extern fn LLVMSetInitializer(GlobalVar: *const Value, ConstantVal: *const Value) void; - -pub const getParam = LLVMGetParam; -extern fn LLVMGetParam(Fn: *const Value, Index: c_uint) *const Value; - -pub const getEnumAttributeKindForName = LLVMGetEnumAttributeKindForName; -extern fn LLVMGetEnumAttributeKindForName(Name: [*]const u8, SLen: usize) c_uint; - -pub const Attribute = opaque {}; - -pub const Builder = opaque { - pub const dispose = LLVMDisposeBuilder; - extern fn LLVMDisposeBuilder(Builder: *const Builder) void; - - pub const positionBuilder = LLVMPositionBuilder; - extern fn LLVMPositionBuilder(Builder: *const Builder, Block: *const BasicBlock, Instr: *const Value) void; - - pub const positionBuilderAtEnd = LLVMPositionBuilderAtEnd; - extern fn LLVMPositionBuilderAtEnd(Builder: *const Builder, Block: *const BasicBlock) void; - - pub const getInsertBlock = LLVMGetInsertBlock; - extern fn LLVMGetInsertBlock(Builder: *const Builder) *const BasicBlock; - - pub const buildCall = LLVMBuildCall; - extern fn LLVMBuildCall(*const Builder, Fn: *const Value, Args: ?[*]*const Value, NumArgs: c_uint, Name: [*:0]const u8) *const Value; - - pub const buildCall2 = LLVMBuildCall2; - extern fn LLVMBuildCall2(*const Builder, *const Type, Fn: *const Value, Args: [*]*const Value, NumArgs: c_uint, Name: [*:0]const u8) *const Value; - - pub const buildRetVoid = LLVMBuildRetVoid; - extern fn LLVMBuildRetVoid(*const Builder) *const Value; - - pub const buildRet = LLVMBuildRet; - extern fn LLVMBuildRet(*const Builder, V: *const Value) *const Value; - - pub const buildUnreachable = LLVMBuildUnreachable; - extern fn LLVMBuildUnreachable(*const Builder) *const Value; - - pub const buildAlloca = LLVMBuildAlloca; - extern fn LLVMBuildAlloca(*const Builder, Ty: *const Type, Name: [*:0]const u8) *const Value; - - pub const buildStore = LLVMBuildStore; - extern fn LLVMBuildStore(*const Builder, Val: *const Value, Ptr: *const Value) *const Value; - - pub const buildLoad = LLVMBuildLoad; - extern fn LLVMBuildLoad(*const Builder, PointerVal: *const Value, Name: [*:0]const u8) *const Value; - - pub const buildNot = LLVMBuildNot; - extern fn LLVMBuildNot(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value; - - pub const buildNSWAdd = LLVMBuildNSWAdd; - extern fn LLVMBuildNSWAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; - - pub const buildNUWAdd = LLVMBuildNUWAdd; - extern fn LLVMBuildNUWAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; - - pub const buildNSWSub = LLVMBuildNSWSub; - extern fn LLVMBuildNSWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; - - pub const buildNUWSub = LLVMBuildNUWSub; - extern fn LLVMBuildNUWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; - - pub const buildIntCast2 = LLVMBuildIntCast2; - extern fn LLVMBuildIntCast2(*const Builder, Val: *const Value, DestTy: *const Type, IsSigned: LLVMBool, Name: [*:0]const u8) *const Value; - - pub const buildBitCast = LLVMBuildBitCast; - extern fn LLVMBuildBitCast(*const Builder, Val: *const Value, DestTy: *const Type, Name: [*:0]const u8) *const Value; - - pub const buildInBoundsGEP = LLVMBuildInBoundsGEP; - extern fn LLVMBuildInBoundsGEP(B: *const Builder, Pointer: *const Value, Indices: [*]*const Value, NumIndices: c_uint, Name: [*:0]const u8) *const Value; -}; - -pub const BasicBlock = opaque { - pub const deleteBasicBlock = LLVMDeleteBasicBlock; - extern fn LLVMDeleteBasicBlock(BB: *const BasicBlock) void; - - pub const getFirstInstruction = LLVMGetFirstInstruction; - extern fn LLVMGetFirstInstruction(BB: *const BasicBlock) ?*const Value; -}; - -pub const TargetMachine = opaque { - pub const create = LLVMCreateTargetMachine; - extern fn LLVMCreateTargetMachine( - T: *const Target, - Triple: [*:0]const u8, - CPU: [*:0]const u8, - Features: [*:0]const u8, - Level: CodeGenOptLevel, - Reloc: RelocMode, - CodeModel: CodeMode, - ) *const TargetMachine; - - pub const dispose = LLVMDisposeTargetMachine; - extern fn LLVMDisposeTargetMachine(T: *const TargetMachine) void; - - pub const emitToFile = LLVMTargetMachineEmitToFile; - extern fn LLVMTargetMachineEmitToFile(*const TargetMachine, M: *const Module, Filename: [*:0]const u8, codegen: CodeGenFileType, ErrorMessage: *[*:0]const u8) LLVMBool; -}; - -pub const CodeMode = extern enum { - Default, - JITDefault, - Tiny, - Small, - Kernel, - Medium, - Large, -}; - -pub const CodeGenOptLevel = extern enum { - None, - Less, - Default, - Aggressive, -}; - -pub const RelocMode = extern enum { - Default, - Static, - PIC, - DynamicNoPic, - ROPI, - RWPI, - ROPI_RWPI, -}; - -pub const CodeGenFileType = extern enum { - AssemblyFile, - ObjectFile, -}; - -pub const Target = opaque { - pub const getFromTriple = LLVMGetTargetFromTriple; - extern fn LLVMGetTargetFromTriple(Triple: [*:0]const u8, T: **const Target, ErrorMessage: *[*:0]const u8) LLVMBool; -}; - -extern fn LLVMInitializeAArch64TargetInfo() void; -extern fn LLVMInitializeAMDGPUTargetInfo() void; -extern fn LLVMInitializeARMTargetInfo() void; -extern fn LLVMInitializeAVRTargetInfo() void; -extern fn LLVMInitializeBPFTargetInfo() void; -extern fn LLVMInitializeHexagonTargetInfo() void; -extern fn LLVMInitializeLanaiTargetInfo() void; -extern fn LLVMInitializeMipsTargetInfo() void; -extern fn LLVMInitializeMSP430TargetInfo() void; -extern fn LLVMInitializeNVPTXTargetInfo() void; -extern fn LLVMInitializePowerPCTargetInfo() void; -extern fn LLVMInitializeRISCVTargetInfo() void; -extern fn LLVMInitializeSparcTargetInfo() void; -extern fn LLVMInitializeSystemZTargetInfo() void; -extern fn LLVMInitializeWebAssemblyTargetInfo() void; -extern fn LLVMInitializeX86TargetInfo() void; -extern fn LLVMInitializeXCoreTargetInfo() void; -extern fn LLVMInitializeAArch64Target() void; -extern fn LLVMInitializeAMDGPUTarget() void; -extern fn LLVMInitializeARMTarget() void; -extern fn LLVMInitializeAVRTarget() void; -extern fn LLVMInitializeBPFTarget() void; -extern fn LLVMInitializeHexagonTarget() void; -extern fn LLVMInitializeLanaiTarget() void; -extern fn LLVMInitializeMipsTarget() void; -extern fn LLVMInitializeMSP430Target() void; -extern fn LLVMInitializeNVPTXTarget() void; -extern fn LLVMInitializePowerPCTarget() void; -extern fn LLVMInitializeRISCVTarget() void; -extern fn LLVMInitializeSparcTarget() void; -extern fn LLVMInitializeSystemZTarget() void; -extern fn LLVMInitializeWebAssemblyTarget() void; -extern fn LLVMInitializeX86Target() void; -extern fn LLVMInitializeXCoreTarget() void; -extern fn LLVMInitializeAArch64TargetMC() void; -extern fn LLVMInitializeAMDGPUTargetMC() void; -extern fn LLVMInitializeARMTargetMC() void; -extern fn LLVMInitializeAVRTargetMC() void; -extern fn LLVMInitializeBPFTargetMC() void; -extern fn LLVMInitializeHexagonTargetMC() void; -extern fn LLVMInitializeLanaiTargetMC() void; -extern fn LLVMInitializeMipsTargetMC() void; -extern fn LLVMInitializeMSP430TargetMC() void; -extern fn LLVMInitializeNVPTXTargetMC() void; -extern fn LLVMInitializePowerPCTargetMC() void; -extern fn LLVMInitializeRISCVTargetMC() void; -extern fn LLVMInitializeSparcTargetMC() void; -extern fn LLVMInitializeSystemZTargetMC() void; -extern fn LLVMInitializeWebAssemblyTargetMC() void; -extern fn LLVMInitializeX86TargetMC() void; -extern fn LLVMInitializeXCoreTargetMC() void; -extern fn LLVMInitializeAArch64AsmPrinter() void; -extern fn LLVMInitializeAMDGPUAsmPrinter() void; -extern fn LLVMInitializeARMAsmPrinter() void; -extern fn LLVMInitializeAVRAsmPrinter() void; -extern fn LLVMInitializeBPFAsmPrinter() void; -extern fn LLVMInitializeHexagonAsmPrinter() void; -extern fn LLVMInitializeLanaiAsmPrinter() void; -extern fn LLVMInitializeMipsAsmPrinter() void; -extern fn LLVMInitializeMSP430AsmPrinter() void; -extern fn LLVMInitializeNVPTXAsmPrinter() void; -extern fn LLVMInitializePowerPCAsmPrinter() void; -extern fn LLVMInitializeRISCVAsmPrinter() void; -extern fn LLVMInitializeSparcAsmPrinter() void; -extern fn LLVMInitializeSystemZAsmPrinter() void; -extern fn LLVMInitializeWebAssemblyAsmPrinter() void; -extern fn LLVMInitializeX86AsmPrinter() void; -extern fn LLVMInitializeXCoreAsmPrinter() void; -extern fn LLVMInitializeAArch64AsmParser() void; -extern fn LLVMInitializeAMDGPUAsmParser() void; -extern fn LLVMInitializeARMAsmParser() void; -extern fn LLVMInitializeAVRAsmParser() void; -extern fn LLVMInitializeBPFAsmParser() void; -extern fn LLVMInitializeHexagonAsmParser() void; -extern fn LLVMInitializeLanaiAsmParser() void; -extern fn LLVMInitializeMipsAsmParser() void; -extern fn LLVMInitializeMSP430AsmParser() void; -extern fn LLVMInitializePowerPCAsmParser() void; -extern fn LLVMInitializeRISCVAsmParser() void; -extern fn LLVMInitializeSparcAsmParser() void; -extern fn LLVMInitializeSystemZAsmParser() void; -extern fn LLVMInitializeWebAssemblyAsmParser() void; -extern fn LLVMInitializeX86AsmParser() void; - -pub const initializeAllTargetInfos = LLVMInitializeAllTargetInfos; -fn LLVMInitializeAllTargetInfos() callconv(.C) void { - LLVMInitializeAArch64TargetInfo(); - LLVMInitializeAMDGPUTargetInfo(); - LLVMInitializeARMTargetInfo(); - LLVMInitializeAVRTargetInfo(); - LLVMInitializeBPFTargetInfo(); - LLVMInitializeHexagonTargetInfo(); - LLVMInitializeLanaiTargetInfo(); - LLVMInitializeMipsTargetInfo(); - LLVMInitializeMSP430TargetInfo(); - LLVMInitializeNVPTXTargetInfo(); - LLVMInitializePowerPCTargetInfo(); - LLVMInitializeRISCVTargetInfo(); - LLVMInitializeSparcTargetInfo(); - LLVMInitializeSystemZTargetInfo(); - LLVMInitializeWebAssemblyTargetInfo(); - LLVMInitializeX86TargetInfo(); - LLVMInitializeXCoreTargetInfo(); -} -pub const initializeAllTargets = LLVMInitializeAllTargets; -fn LLVMInitializeAllTargets() callconv(.C) void { - LLVMInitializeAArch64Target(); - LLVMInitializeAMDGPUTarget(); - LLVMInitializeARMTarget(); - LLVMInitializeAVRTarget(); - LLVMInitializeBPFTarget(); - LLVMInitializeHexagonTarget(); - LLVMInitializeLanaiTarget(); - LLVMInitializeMipsTarget(); - LLVMInitializeMSP430Target(); - LLVMInitializeNVPTXTarget(); - LLVMInitializePowerPCTarget(); - LLVMInitializeRISCVTarget(); - LLVMInitializeSparcTarget(); - LLVMInitializeSystemZTarget(); - LLVMInitializeWebAssemblyTarget(); - LLVMInitializeX86Target(); - LLVMInitializeXCoreTarget(); -} -pub const initializeAllTargetMCs = LLVMInitializeAllTargetMCs; -fn LLVMInitializeAllTargetMCs() callconv(.C) void { - LLVMInitializeAArch64TargetMC(); - LLVMInitializeAMDGPUTargetMC(); - LLVMInitializeARMTargetMC(); - LLVMInitializeAVRTargetMC(); - LLVMInitializeBPFTargetMC(); - LLVMInitializeHexagonTargetMC(); - LLVMInitializeLanaiTargetMC(); - LLVMInitializeMipsTargetMC(); - LLVMInitializeMSP430TargetMC(); - LLVMInitializeNVPTXTargetMC(); - LLVMInitializePowerPCTargetMC(); - LLVMInitializeRISCVTargetMC(); - LLVMInitializeSparcTargetMC(); - LLVMInitializeSystemZTargetMC(); - LLVMInitializeWebAssemblyTargetMC(); - LLVMInitializeX86TargetMC(); - LLVMInitializeXCoreTargetMC(); -} -pub const initializeAllAsmPrinters = LLVMInitializeAllAsmPrinters; -fn LLVMInitializeAllAsmPrinters() callconv(.C) void { - LLVMInitializeAArch64AsmPrinter(); - LLVMInitializeAMDGPUAsmPrinter(); - LLVMInitializeARMAsmPrinter(); - LLVMInitializeAVRAsmPrinter(); - LLVMInitializeBPFAsmPrinter(); - LLVMInitializeHexagonAsmPrinter(); - LLVMInitializeLanaiAsmPrinter(); - LLVMInitializeMipsAsmPrinter(); - LLVMInitializeMSP430AsmPrinter(); - LLVMInitializeNVPTXAsmPrinter(); - LLVMInitializePowerPCAsmPrinter(); - LLVMInitializeRISCVAsmPrinter(); - LLVMInitializeSparcAsmPrinter(); - LLVMInitializeSystemZAsmPrinter(); - LLVMInitializeWebAssemblyAsmPrinter(); - LLVMInitializeX86AsmPrinter(); - LLVMInitializeXCoreAsmPrinter(); -} -pub const initializeAllAsmParsers = LLVMInitializeAllAsmParsers; -fn LLVMInitializeAllAsmParsers() callconv(.C) void { - LLVMInitializeAArch64AsmParser(); - LLVMInitializeAMDGPUAsmParser(); - LLVMInitializeARMAsmParser(); - LLVMInitializeAVRAsmParser(); - LLVMInitializeBPFAsmParser(); - LLVMInitializeHexagonAsmParser(); - LLVMInitializeLanaiAsmParser(); - LLVMInitializeMipsAsmParser(); - LLVMInitializeMSP430AsmParser(); - LLVMInitializePowerPCAsmParser(); - LLVMInitializeRISCVAsmParser(); - LLVMInitializeSparcAsmParser(); - LLVMInitializeSystemZAsmParser(); - LLVMInitializeWebAssemblyAsmParser(); - LLVMInitializeX86AsmParser(); -} - -extern fn ZigLLDLinkCOFF(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; -extern fn ZigLLDLinkELF(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; -extern fn ZigLLDLinkMachO(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; -extern fn ZigLLDLinkWasm(argc: c_int, argv: [*:null]const ?[*:0]const u8, can_exit_early: bool) c_int; - -pub const LinkCOFF = ZigLLDLinkCOFF; -pub const LinkELF = ZigLLDLinkELF; -pub const LinkMachO = ZigLLDLinkMachO; -pub const LinkWasm = ZigLLDLinkWasm; - -pub const ObjectFormatType = extern enum(c_int) { - Unknown, - COFF, - ELF, - MachO, - Wasm, - XCOFF, -}; - -pub const GetHostCPUName = LLVMGetHostCPUName; -extern fn LLVMGetHostCPUName() ?[*:0]u8; - -pub const GetNativeFeatures = ZigLLVMGetNativeFeatures; -extern fn ZigLLVMGetNativeFeatures() ?[*:0]u8; - -pub const WriteArchive = ZigLLVMWriteArchive; -extern fn ZigLLVMWriteArchive( - archive_name: [*:0]const u8, - file_names_ptr: [*]const [*:0]const u8, - file_names_len: usize, - os_type: OSType, -) bool; - -pub const OSType = extern enum(c_int) { - UnknownOS = 0, - Ananas = 1, - CloudABI = 2, - Darwin = 3, - DragonFly = 4, - FreeBSD = 5, - Fuchsia = 6, - IOS = 7, - KFreeBSD = 8, - Linux = 9, - Lv2 = 10, - MacOSX = 11, - NetBSD = 12, - OpenBSD = 13, - Solaris = 14, - Win32 = 15, - Haiku = 16, - Minix = 17, - RTEMS = 18, - NaCl = 19, - CNK = 20, - AIX = 21, - CUDA = 22, - NVCL = 23, - AMDHSA = 24, - PS4 = 25, - ELFIAMCU = 26, - TvOS = 27, - WatchOS = 28, - Mesa3D = 29, - Contiki = 30, - AMDPAL = 31, - HermitCore = 32, - Hurd = 33, - WASI = 34, - Emscripten = 35, -}; - -pub const ArchType = extern enum(c_int) { - UnknownArch = 0, - arm = 1, - armeb = 2, - aarch64 = 3, - aarch64_be = 4, - aarch64_32 = 5, - arc = 6, - avr = 7, - bpfel = 8, - bpfeb = 9, - hexagon = 10, - mips = 11, - mipsel = 12, - mips64 = 13, - mips64el = 14, - msp430 = 15, - ppc = 16, - ppc64 = 17, - ppc64le = 18, - r600 = 19, - amdgcn = 20, - riscv32 = 21, - riscv64 = 22, - sparc = 23, - sparcv9 = 24, - sparcel = 25, - systemz = 26, - tce = 27, - tcele = 28, - thumb = 29, - thumbeb = 30, - x86 = 31, - x86_64 = 32, - xcore = 33, - nvptx = 34, - nvptx64 = 35, - le32 = 36, - le64 = 37, - amdil = 38, - amdil64 = 39, - hsail = 40, - hsail64 = 41, - spir = 42, - spir64 = 43, - kalimba = 44, - shave = 45, - lanai = 46, - wasm32 = 47, - wasm64 = 48, - renderscript32 = 49, - renderscript64 = 50, - ve = 51, -}; - -pub const ParseCommandLineOptions = ZigLLVMParseCommandLineOptions; -extern fn ZigLLVMParseCommandLineOptions(argc: usize, argv: [*]const [*:0]const u8) void; - -pub const WriteImportLibrary = ZigLLVMWriteImportLibrary; -extern fn ZigLLVMWriteImportLibrary( - def_path: [*:0]const u8, - arch: ArchType, - output_lib_path: [*c]const u8, - kill_at: bool, -) bool; diff --git a/src/main.zig b/src/main.zig index f026b6b4b4..ac31437f85 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1703,7 +1703,7 @@ fn buildOutputType( if (build_options.have_llvm and emit_asm != .no) { // LLVM has no way to set this non-globally. const argv = [_][*:0]const u8{ "zig (LLVM option parsing)", "--x86-asm-syntax=intel" }; - @import("llvm_bindings.zig").ParseCommandLineOptions(argv.len, &argv); + @import("codegen/llvm/bindings.zig").ParseCommandLineOptions(argv.len, &argv); } gimmeMoreOfThoseSweetSweetFileDescriptors(); @@ -2890,7 +2890,7 @@ pub fn punt_to_lld(arena: *Allocator, args: []const []const u8) error{OutOfMemor argv[i] = try arena.dupeZ(u8, arg); // TODO If there was an argsAllocZ we could avoid this allocation. } const exit_code = rc: { - const llvm = @import("llvm_bindings.zig"); + const llvm = @import("codegen/llvm/bindings.zig"); const argc = @intCast(c_int, argv.len); if (mem.eql(u8, args[1], "ld.lld")) { break :rc llvm.LinkELF(argc, argv.ptr, true); @@ -3275,7 +3275,7 @@ fn detectNativeTargetInfo(gpa: *Allocator, cross_target: std.zig.CrossTarget) !s if (!build_options.have_llvm) fatal("CPU features detection is not yet available for {s} without LLVM extensions", .{@tagName(arch)}); - const llvm = @import("llvm_bindings.zig"); + const llvm = @import("codegen/llvm/bindings.zig"); const llvm_cpu_name = llvm.GetHostCPUName(); const llvm_cpu_features = llvm.GetNativeFeatures(); info.target.cpu = try detectNativeCpuWithLLVM(arch, llvm_cpu_name, llvm_cpu_features); diff --git a/src/mingw.zig b/src/mingw.zig index d55cc28b2b..0a7669f995 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -405,7 +405,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { }); errdefer comp.gpa.free(lib_final_path); - const llvm = @import("llvm_bindings.zig"); + const llvm = @import("codegen/llvm/bindings.zig"); const arch_type = @import("target.zig").archToLLVM(target.cpu.arch); const def_final_path_z = try arena.dupeZ(u8, def_final_path); const lib_final_path_z = try arena.dupeZ(u8, lib_final_path); diff --git a/src/target.zig b/src/target.zig index d33e8d06b0..daac577c7b 100644 --- a/src/target.zig +++ b/src/target.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const llvm = @import("llvm_bindings.zig"); +const llvm = @import("codegen/llvm/bindings.zig"); pub const ArchOsAbi = struct { arch: std.Target.Cpu.Arch, diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig new file mode 100644 index 0000000000..ebb5fc390f --- /dev/null +++ b/test/stage2/llvm.zig @@ -0,0 +1,43 @@ +const std = @import("std"); +const TestContext = @import("../../src/test.zig").TestContext; +const build_options = @import("build_options"); + +// These tests should work with all platforms, but we're using linux_x64 for +// now for consistency. Will be expanded eventually. +const linux_x64 = std.zig.CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .linux, +}; + +pub fn addCases(ctx: *TestContext) !void { + { + var case = ctx.exeUsingLlvmBackend("simple addition and subtraction", linux_x64); + + case.addCompareOutput( + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\ + \\export fn main() c_int { + \\ var a: i32 = -5; + \\ const x = add(a, 7); + \\ var y = add(2, 0); + \\ y -= x; + \\ return y; + \\} + , ""); + } + + { + var case = ctx.exeUsingLlvmBackend("hello world", linux_x64); + + case.addCompareOutput( + \\extern fn puts(s: [*:0]const u8) c_int; + \\ + \\export fn main() c_int { + \\ _ = puts("hello world!"); + \\ return 0; + \\} + , "hello world!" ++ std.cstr.line_sep); + } +} diff --git a/test/stage2/llvm_backend.zig b/test/stage2/llvm_backend.zig deleted file mode 100644 index ebb5fc390f..0000000000 --- a/test/stage2/llvm_backend.zig +++ /dev/null @@ -1,43 +0,0 @@ -const std = @import("std"); -const TestContext = @import("../../src/test.zig").TestContext; -const build_options = @import("build_options"); - -// These tests should work with all platforms, but we're using linux_x64 for -// now for consistency. Will be expanded eventually. -const linux_x64 = std.zig.CrossTarget{ - .cpu_arch = .x86_64, - .os_tag = .linux, -}; - -pub fn addCases(ctx: *TestContext) !void { - { - var case = ctx.exeUsingLlvmBackend("simple addition and subtraction", linux_x64); - - case.addCompareOutput( - \\fn add(a: i32, b: i32) i32 { - \\ return a + b; - \\} - \\ - \\export fn main() c_int { - \\ var a: i32 = -5; - \\ const x = add(a, 7); - \\ var y = add(2, 0); - \\ y -= x; - \\ return y; - \\} - , ""); - } - - { - var case = ctx.exeUsingLlvmBackend("hello world", linux_x64); - - case.addCompareOutput( - \\extern fn puts(s: [*:0]const u8) c_int; - \\ - \\export fn main() c_int { - \\ _ = puts("hello world!"); - \\ return 0; - \\} - , "hello world!" ++ std.cstr.line_sep); - } -} diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 6e25dc283b..5c21ccd669 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -31,7 +31,7 @@ pub fn addCases(ctx: *TestContext) !void { try @import("spu-ii.zig").addCases(ctx); try @import("arm.zig").addCases(ctx); try @import("aarch64.zig").addCases(ctx); - try @import("llvm_backend.zig").addCases(ctx); + try @import("llvm.zig").addCases(ctx); { var case = ctx.exe("hello world with updates", linux_x64); -- cgit v1.2.3 From a9b505fa7774e2e8451bedfa7bea27d7227572e7 Mon Sep 17 00:00:00 2001 From: Jay Petacat Date: Tue, 5 Jan 2021 20:57:18 -0500 Subject: Reduce use of deprecated IO types Related: #4917 --- doc/docgen.zig | 8 ++++---- lib/std/Progress.zig | 2 +- lib/std/atomic/queue.zig | 8 ++++---- lib/std/build.zig | 10 +++++----- lib/std/build/run.zig | 4 ++-- lib/std/child_process.zig | 2 +- lib/std/coff.zig | 8 ++++---- lib/std/crypto/benchmark.zig | 2 +- lib/std/debug.zig | 36 +++++++++++++++++------------------ lib/std/dwarf.zig | 10 +++++----- lib/std/heap/logging_allocator.zig | 2 +- lib/std/io/buffered_atomic_file.zig | 2 +- lib/std/io/fixed_buffer_stream.zig | 2 +- lib/std/io/test.zig | 6 +++--- lib/std/json.zig | 16 ++++++++-------- lib/std/json/write_stream.zig | 2 +- lib/std/net.zig | 4 ++-- lib/std/net/test.zig | 2 +- lib/std/os.zig | 2 +- lib/std/os/test.zig | 6 +++--- lib/std/special/build_runner.zig | 4 ++-- lib/std/unicode/throughput_test.zig | 2 +- lib/std/zig/cross_target.zig | 14 +++++++------- lib/std/zig/parser_test.zig | 6 +++--- lib/std/zig/perf_test.zig | 2 +- lib/std/zig/render.zig | 4 ++-- src/Cache.zig | 2 +- src/Compilation.zig | 8 ++++---- src/DepTokenizer.zig | 2 +- src/Module.zig | 2 +- src/codegen/llvm.zig | 8 ++++---- src/libc_installation.zig | 6 +++--- src/main.zig | 38 ++++++++++++++++++------------------- src/print_env.zig | 10 +++++----- src/print_targets.zig | 10 +++++----- src/test.zig | 2 +- src/translate_c.zig | 2 +- src/zir.zig | 4 ++-- test/compare_output.zig | 30 ++++++++++++++--------------- test/stage1/behavior/bugs/5487.zig | 6 +++--- test/tests.zig | 8 ++++---- tools/update_clang_options.zig | 8 ++++---- tools/update_glibc.zig | 6 +++--- 43 files changed, 159 insertions(+), 159 deletions(-) (limited to 'src/codegen') diff --git a/doc/docgen.zig b/doc/docgen.zig index 7e0bedbb6c..90e3e32201 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -40,9 +40,9 @@ pub fn main() !void { var out_file = try fs.cwd().createFile(out_file_name, .{}); defer out_file.close(); - const input_file_bytes = try in_file.inStream().readAllAlloc(allocator, max_doc_file_size); + const input_file_bytes = try in_file.reader().readAllAlloc(allocator, max_doc_file_size); - var buffered_out_stream = io.bufferedOutStream(out_file.writer()); + var buffered_writer = io.bufferedWriter(out_file.writer()); var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); var toc = try genToc(allocator, &tokenizer); @@ -50,8 +50,8 @@ pub fn main() !void { try fs.cwd().makePath(tmp_dir_name); defer fs.cwd().deleteTree(tmp_dir_name) catch {}; - try genHtml(allocator, &tokenizer, &toc, buffered_out_stream.writer(), zig_exe); - try buffered_out_stream.flush(); + try genHtml(allocator, &tokenizer, &toc, buffered_writer.writer(), zig_exe); + try buffered_writer.flush(); } const Token = struct { diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 6226e34248..ca9fb8ea1f 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -271,7 +271,7 @@ fn refreshWithHeldLock(self: *Progress) void { pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void { const file = self.terminal orelse return; self.refresh(); - file.outStream().print(format, args) catch { + file.writer().print(format, args) catch { self.terminal = null; return; }; diff --git a/lib/std/atomic/queue.zig b/lib/std/atomic/queue.zig index 19d04d041a..fa3711cd9f 100644 --- a/lib/std/atomic/queue.zig +++ b/lib/std/atomic/queue.zig @@ -122,7 +122,7 @@ pub fn Queue(comptime T: type) type { /// Dumps the contents of the queue to `stderr`. pub fn dump(self: *Self) void { - self.dumpToStream(std.io.getStdErr().outStream()) catch return; + self.dumpToStream(std.io.getStdErr().writer()) catch return; } /// Dumps the contents of the queue to `stream`. @@ -351,7 +351,7 @@ test "std.atomic.Queue dump" { // Test empty stream fbs.reset(); - try queue.dumpToStream(fbs.outStream()); + try queue.dumpToStream(fbs.writer()); expect(mem.eql(u8, buffer[0..fbs.pos], \\head: (null) \\tail: (null) @@ -367,7 +367,7 @@ test "std.atomic.Queue dump" { queue.put(&node_0); fbs.reset(); - try queue.dumpToStream(fbs.outStream()); + try queue.dumpToStream(fbs.writer()); var expected = try std.fmt.bufPrint(expected_buffer[0..], \\head: 0x{x}=1 @@ -387,7 +387,7 @@ test "std.atomic.Queue dump" { queue.put(&node_1); fbs.reset(); - try queue.dumpToStream(fbs.outStream()); + try queue.dumpToStream(fbs.writer()); expected = try std.fmt.bufPrint(expected_buffer[0..], \\head: 0x{x}=1 diff --git a/lib/std/build.zig b/lib/std/build.zig index 0d17b4753a..cb4cb229e3 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1042,7 +1042,7 @@ pub const Builder = struct { try child.spawn(); - const stdout = try child.stdout.?.inStream().readAllAlloc(self.allocator, max_output_size); + const stdout = try child.stdout.?.reader().readAllAlloc(self.allocator, max_output_size); errdefer self.allocator.free(stdout); const term = try child.wait(); @@ -1849,7 +1849,7 @@ pub const LibExeObjStep = struct { } pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void { - const out = self.build_options_contents.outStream(); + const out = self.build_options_contents.writer(); switch (T) { []const []const u8 => { out.print("pub const {z}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable; @@ -2295,16 +2295,16 @@ pub const LibExeObjStep = struct { } else { var mcpu_buffer = std.ArrayList(u8).init(builder.allocator); - try mcpu_buffer.outStream().print("-mcpu={s}", .{cross.cpu.model.name}); + try mcpu_buffer.writer().print("-mcpu={s}", .{cross.cpu.model.name}); for (all_features) |feature, i_usize| { const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize); const in_cpu_set = populated_cpu_features.isEnabled(i); const in_actual_set = cross.cpu.features.isEnabled(i); if (in_cpu_set and !in_actual_set) { - try mcpu_buffer.outStream().print("-{s}", .{feature.name}); + try mcpu_buffer.writer().print("-{s}", .{feature.name}); } else if (!in_cpu_set and in_actual_set) { - try mcpu_buffer.outStream().print("+{s}", .{feature.name}); + try mcpu_buffer.writer().print("+{s}", .{feature.name}); } } diff --git a/lib/std/build/run.zig b/lib/std/build/run.zig index 36a4a1a843..8f8fa2eba0 100644 --- a/lib/std/build/run.zig +++ b/lib/std/build/run.zig @@ -200,7 +200,7 @@ pub const RunStep = struct { switch (self.stdout_action) { .expect_exact, .expect_matches => { - stdout = child.stdout.?.inStream().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; + stdout = child.stdout.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; }, .inherit, .ignore => {}, } @@ -210,7 +210,7 @@ pub const RunStep = struct { switch (self.stderr_action) { .expect_exact, .expect_matches => { - stderr = child.stderr.?.inStream().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; + stderr = child.stderr.?.reader().readAllAlloc(self.builder.allocator, max_stdout_size) catch unreachable; }, .inherit, .ignore => {}, } diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 4360cc7d73..d37dd9fdf5 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -922,7 +922,7 @@ fn writeIntFd(fd: i32, value: ErrInt) !void { .capable_io_mode = .blocking, .intended_io_mode = .blocking, }; - file.outStream().writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources; + file.writer().writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources; } fn readIntFd(fd: i32) !ErrInt { diff --git a/lib/std/coff.zig b/lib/std/coff.zig index fdc2ec5a82..85000bf8cb 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -127,7 +127,7 @@ pub const Coff = struct { pub fn loadHeader(self: *Coff) !void { const pe_pointer_offset = 0x3C; - const in = self.in_file.inStream(); + const in = self.in_file.reader(); var magic: [2]u8 = undefined; try in.readNoEof(magic[0..]); @@ -163,7 +163,7 @@ pub const Coff = struct { } fn loadOptionalHeader(self: *Coff) !void { - const in = self.in_file.inStream(); + const in = self.in_file.reader(); self.pe_header.magic = try in.readIntLittle(u16); // For now we're only interested in finding the reference to the .pdb, // so we'll skip most of this header, which size is different in 32 @@ -206,7 +206,7 @@ pub const Coff = struct { const debug_dir = &self.pe_header.data_directory[DEBUG_DIRECTORY]; const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data; - const in = self.in_file.inStream(); + const in = self.in_file.reader(); try self.in_file.seekTo(file_offset); // Find the correct DebugDirectoryEntry, and where its data is stored. @@ -257,7 +257,7 @@ pub const Coff = struct { try self.sections.ensureCapacity(self.coff_header.number_of_sections); - const in = self.in_file.inStream(); + const in = self.in_file.reader(); var name: [8]u8 = undefined; diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 5b3b837d13..00336aef87 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -314,7 +314,7 @@ fn mode(comptime x: comptime_int) comptime_int { } pub fn main() !void { - const stdout = std.io.getStdOut().outStream(); + const stdout = std.io.getStdOut().writer(); var buffer: [1024]u8 = undefined; var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 073f68da5d..b19f96892f 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -517,15 +517,15 @@ fn populateModule(di: *ModuleDebugInfo, mod: *Module) !void { const modi = di.pdb.getStreamById(mod.mod_info.ModuleSymStream) orelse return error.MissingDebugInfo; - const signature = try modi.inStream().readIntLittle(u32); + const signature = try modi.reader().readIntLittle(u32); if (signature != 4) return error.InvalidDebugInfo; mod.symbols = try allocator.alloc(u8, mod.mod_info.SymByteSize - 4); - try modi.inStream().readNoEof(mod.symbols); + try modi.reader().readNoEof(mod.symbols); mod.subsect_info = try allocator.alloc(u8, mod.mod_info.C13ByteSize); - try modi.inStream().readNoEof(mod.subsect_info); + try modi.reader().readNoEof(mod.subsect_info); var sect_offset: usize = 0; var skip_len: usize = undefined; @@ -704,11 +704,11 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf try di.pdb.openFile(di.coff, path); var pdb_stream = di.pdb.getStream(pdb.StreamType.Pdb) orelse return error.InvalidDebugInfo; - const version = try pdb_stream.inStream().readIntLittle(u32); - const signature = try pdb_stream.inStream().readIntLittle(u32); - const age = try pdb_stream.inStream().readIntLittle(u32); + const version = try pdb_stream.reader().readIntLittle(u32); + const signature = try pdb_stream.reader().readIntLittle(u32); + const age = try pdb_stream.reader().readIntLittle(u32); var guid: [16]u8 = undefined; - try pdb_stream.inStream().readNoEof(&guid); + try pdb_stream.reader().readNoEof(&guid); if (version != 20000404) // VC70, only value observed by LLVM team return error.UnknownPDBVersion; if (!mem.eql(u8, &di.coff.guid, &guid) or di.coff.age != age) @@ -716,9 +716,9 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf // We validated the executable and pdb match. const string_table_index = str_tab_index: { - const name_bytes_len = try pdb_stream.inStream().readIntLittle(u32); + const name_bytes_len = try pdb_stream.reader().readIntLittle(u32); const name_bytes = try allocator.alloc(u8, name_bytes_len); - try pdb_stream.inStream().readNoEof(name_bytes); + try pdb_stream.reader().readNoEof(name_bytes); const HashTableHeader = packed struct { Size: u32, @@ -728,17 +728,17 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf return cap * 2 / 3 + 1; } }; - const hash_tbl_hdr = try pdb_stream.inStream().readStruct(HashTableHeader); + const hash_tbl_hdr = try pdb_stream.reader().readStruct(HashTableHeader); if (hash_tbl_hdr.Capacity == 0) return error.InvalidDebugInfo; if (hash_tbl_hdr.Size > HashTableHeader.maxLoad(hash_tbl_hdr.Capacity)) return error.InvalidDebugInfo; - const present = try readSparseBitVector(&pdb_stream.inStream(), allocator); + const present = try readSparseBitVector(&pdb_stream.reader(), allocator); if (present.len != hash_tbl_hdr.Size) return error.InvalidDebugInfo; - const deleted = try readSparseBitVector(&pdb_stream.inStream(), allocator); + const deleted = try readSparseBitVector(&pdb_stream.reader(), allocator); const Bucket = struct { first: u32, @@ -746,8 +746,8 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf }; const bucket_list = try allocator.alloc(Bucket, present.len); for (present) |_| { - const name_offset = try pdb_stream.inStream().readIntLittle(u32); - const name_index = try pdb_stream.inStream().readIntLittle(u32); + const name_offset = try pdb_stream.reader().readIntLittle(u32); + const name_index = try pdb_stream.reader().readIntLittle(u32); const name = mem.spanZ(std.meta.assumeSentinel(name_bytes.ptr + name_offset, 0)); if (mem.eql(u8, name, "/names")) { break :str_tab_index name_index; @@ -762,7 +762,7 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf const dbi = di.pdb.dbi; // Dbi Header - const dbi_stream_header = try dbi.inStream().readStruct(pdb.DbiStreamHeader); + const dbi_stream_header = try dbi.reader().readStruct(pdb.DbiStreamHeader); if (dbi_stream_header.VersionHeader != 19990903) // V70, only value observed by LLVM team return error.UnknownPDBVersion; if (dbi_stream_header.Age != age) @@ -776,7 +776,7 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf // Module Info Substream var mod_info_offset: usize = 0; while (mod_info_offset != mod_info_size) { - const mod_info = try dbi.inStream().readStruct(pdb.ModInfo); + const mod_info = try dbi.reader().readStruct(pdb.ModInfo); var this_record_len: usize = @sizeOf(pdb.ModInfo); const module_name = try dbi.readNullTermString(allocator); @@ -814,14 +814,14 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf var sect_contribs = ArrayList(pdb.SectionContribEntry).init(allocator); var sect_cont_offset: usize = 0; if (section_contrib_size != 0) { - const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.inStream().readIntLittle(u32)); + const ver = @intToEnum(pdb.SectionContrSubstreamVersion, try dbi.reader().readIntLittle(u32)); if (ver != pdb.SectionContrSubstreamVersion.Ver60) return error.InvalidDebugInfo; sect_cont_offset += @sizeOf(u32); } while (sect_cont_offset != section_contrib_size) { const entry = try sect_contribs.addOne(); - entry.* = try dbi.inStream().readStruct(pdb.SectionContribEntry); + entry.* = try dbi.reader().readStruct(pdb.SectionContribEntry); sect_cont_offset += @sizeOf(pdb.SectionContribEntry); if (sect_cont_offset > section_contrib_size) diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index 2e732cbc75..6769a139da 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -408,7 +408,7 @@ pub const DwarfInfo = struct { fn scanAllFunctions(di: *DwarfInfo) !void { var stream = io.fixedBufferStream(di.debug_info); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); var this_unit_offset: u64 = 0; @@ -512,7 +512,7 @@ pub const DwarfInfo = struct { fn scanAllCompileUnits(di: *DwarfInfo) !void { var stream = io.fixedBufferStream(di.debug_info); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); var this_unit_offset: u64 = 0; @@ -585,7 +585,7 @@ pub const DwarfInfo = struct { if (di.debug_ranges) |debug_ranges| { if (compile_unit.die.getAttrSecOffset(AT_ranges)) |ranges_offset| { var stream = io.fixedBufferStream(debug_ranges); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); // All the addresses in the list are relative to the value @@ -640,7 +640,7 @@ pub const DwarfInfo = struct { fn parseAbbrevTable(di: *DwarfInfo, offset: u64) !AbbrevTable { var stream = io.fixedBufferStream(di.debug_abbrev); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); try seekable.seekTo(offset); @@ -691,7 +691,7 @@ pub const DwarfInfo = struct { pub fn getLineNumberInfo(di: *DwarfInfo, compile_unit: CompileUnit, target_address: usize) !debug.LineInfo { var stream = io.fixedBufferStream(di.debug_line); - const in = &stream.inStream(); + const in = &stream.reader(); const seekable = &stream.seekableStream(); const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT_comp_dir); diff --git a/lib/std/heap/logging_allocator.zig b/lib/std/heap/logging_allocator.zig index 47a584bb1d..7920138e9b 100644 --- a/lib/std/heap/logging_allocator.zig +++ b/lib/std/heap/logging_allocator.zig @@ -89,7 +89,7 @@ test "LoggingAllocator" { var allocator_buf: [10]u8 = undefined; var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf)); - const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.outStream()).allocator; + const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.writer()).allocator; var a = try allocator.alloc(u8, 10); a = allocator.shrink(a, 5); diff --git a/lib/std/io/buffered_atomic_file.zig b/lib/std/io/buffered_atomic_file.zig index 9d65e9d193..66c349d318 100644 --- a/lib/std/io/buffered_atomic_file.zig +++ b/lib/std/io/buffered_atomic_file.zig @@ -38,7 +38,7 @@ pub const BufferedAtomicFile = struct { self.atomic_file = try dir.atomicFile(dest_path, atomic_file_options); errdefer self.atomic_file.deinit(); - self.file_stream = self.atomic_file.file.outStream(); + self.file_stream = self.atomic_file.file.writer(); self.buffered_stream = .{ .unbuffered_writer = self.file_stream }; return self; } diff --git a/lib/std/io/fixed_buffer_stream.zig b/lib/std/io/fixed_buffer_stream.zig index b36949e538..1711b6da1b 100644 --- a/lib/std/io/fixed_buffer_stream.zig +++ b/lib/std/io/fixed_buffer_stream.zig @@ -45,7 +45,7 @@ pub fn FixedBufferStream(comptime Buffer: type) type { return .{ .context = self }; } - /// Deprecated: use `inStream` + /// Deprecated: use `reader` pub fn inStream(self: *Self) InStream { return .{ .context = self }; } diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index 85357ae58a..9fdef0de1d 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -30,8 +30,8 @@ test "write a file, read it, then delete it" { var file = try tmp.dir.createFile(tmp_file_name, .{}); defer file.close(); - var buf_stream = io.bufferedOutStream(file.outStream()); - const st = buf_stream.outStream(); + var buf_stream = io.bufferedWriter(file.writer()); + const st = buf_stream.writer(); try st.print("begin", .{}); try st.writeAll(data[0..]); try st.print("end", .{}); @@ -72,7 +72,7 @@ test "BitStreams with File Stream" { var file = try tmp.dir.createFile(tmp_file_name, .{}); defer file.close(); - var bit_stream = io.bitOutStream(builtin.endian, file.outStream()); + var bit_stream = io.bitWriter(builtin.endian, file.writer()); try bit_stream.writeBits(@as(u2, 1), 1); try bit_stream.writeBits(@as(u5, 2), 2); diff --git a/lib/std/json.zig b/lib/std/json.zig index 87808a7350..5cbdb4319e 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -1323,31 +1323,31 @@ test "Value.jsonStringify" { { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try @as(Value, .Null).jsonStringify(.{}, fbs.outStream()); + try @as(Value, .Null).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "null"); } { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try (Value{ .Bool = true }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .Bool = true }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "true"); } { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .Integer = 42 }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "42"); } { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01"); } { var buffer: [10]u8 = undefined; var fbs = std.io.fixedBufferStream(&buffer); - try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .String = "weeee" }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "\"weeee\""); } { @@ -1360,7 +1360,7 @@ test "Value.jsonStringify" { }; try (Value{ .Array = Array.fromOwnedSlice(undefined, &vals), - }).jsonStringify(.{}, fbs.outStream()); + }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "[1,2,3]"); } { @@ -1369,7 +1369,7 @@ test "Value.jsonStringify" { var obj = ObjectMap.init(testing.allocator); defer obj.deinit(); try obj.putNoClobber("a", .{ .String = "b" }); - try (Value{ .Object = obj }).jsonStringify(.{}, fbs.outStream()); + try (Value{ .Object = obj }).jsonStringify(.{}, fbs.writer()); testing.expectEqualSlices(u8, fbs.getWritten(), "{\"a\":\"b\"}"); } } @@ -2223,7 +2223,7 @@ test "write json then parse it" { var out_buffer: [1000]u8 = undefined; var fixed_buffer_stream = std.io.fixedBufferStream(&out_buffer); - const out_stream = fixed_buffer_stream.outStream(); + const out_stream = fixed_buffer_stream.writer(); var jw = writeStream(out_stream, 4); try jw.beginObject(); diff --git a/lib/std/json/write_stream.zig b/lib/std/json/write_stream.zig index 9322ca5429..b4a8aed84c 100644 --- a/lib/std/json/write_stream.zig +++ b/lib/std/json/write_stream.zig @@ -238,7 +238,7 @@ pub fn writeStream( test "json write stream" { var out_buf: [1024]u8 = undefined; var slice_stream = std.io.fixedBufferStream(&out_buf); - const out = slice_stream.outStream(); + const out = slice_stream.writer(); var arena_allocator = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_allocator.deinit(); diff --git a/lib/std/net.zig b/lib/std/net.zig index da35bd88b0..037df76907 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -1106,7 +1106,7 @@ fn linuxLookupNameFromHosts( }; defer file.close(); - const stream = std.io.bufferedInStream(file.inStream()).inStream(); + const stream = std.io.bufferedReader(file.reader()).reader(); var line_buf: [512]u8 = undefined; while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { error.StreamTooLong => blk: { @@ -1304,7 +1304,7 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { }; defer file.close(); - const stream = std.io.bufferedInStream(file.inStream()).inStream(); + const stream = std.io.bufferedReader(file.reader()).reader(); var line_buf: [512]u8 = undefined; while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) { error.StreamTooLong => blk: { diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index 110c6ec9c0..74ae1ddf4f 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -249,6 +249,6 @@ fn testServer(server: *net.StreamServer) anyerror!void { var client = try server.accept(); - const stream = client.file.outStream(); + const stream = client.file.writer(); try stream.print("hello from server\n", .{}); } diff --git a/lib/std/os.zig b/lib/std/os.zig index e9bf6379cc..42bdf486d7 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -191,7 +191,7 @@ fn getRandomBytesDevURandom(buf: []u8) !void { .capable_io_mode = .blocking, .intended_io_mode = .blocking, }; - const stream = file.inStream(); + const stream = file.reader(); stream.readNoEof(buf) catch return error.Unexpected; } diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 65dee90e62..0904a8585f 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -475,7 +475,7 @@ test "mmap" { const file = try tmp.dir.createFile(test_out_file, .{}); defer file.close(); - const stream = file.outStream(); + const stream = file.writer(); var i: u32 = 0; while (i < alloc_size / @sizeOf(u32)) : (i += 1) { @@ -499,7 +499,7 @@ test "mmap" { defer os.munmap(data); var mem_stream = io.fixedBufferStream(data); - const stream = mem_stream.inStream(); + const stream = mem_stream.reader(); var i: u32 = 0; while (i < alloc_size / @sizeOf(u32)) : (i += 1) { @@ -523,7 +523,7 @@ test "mmap" { defer os.munmap(data); var mem_stream = io.fixedBufferStream(data); - const stream = mem_stream.inStream(); + const stream = mem_stream.reader(); var i: u32 = alloc_size / 2 / @sizeOf(u32); while (i < alloc_size / @sizeOf(u32)) : (i += 1) { diff --git a/lib/std/special/build_runner.zig b/lib/std/special/build_runner.zig index f2fa2dc3b8..0b7baf0fc1 100644 --- a/lib/std/special/build_runner.zig +++ b/lib/std/special/build_runner.zig @@ -57,8 +57,8 @@ pub fn main() !void { var targets = ArrayList([]const u8).init(allocator); - const stderr_stream = io.getStdErr().outStream(); - const stdout_stream = io.getStdOut().outStream(); + const stderr_stream = io.getStdErr().writer(); + const stdout_stream = io.getStdOut().writer(); while (nextArg(args, &arg_idx)) |arg| { if (mem.startsWith(u8, arg, "-D")) { diff --git a/lib/std/unicode/throughput_test.zig b/lib/std/unicode/throughput_test.zig index 2676a30cb2..8f9f9d9cb7 100644 --- a/lib/std/unicode/throughput_test.zig +++ b/lib/std/unicode/throughput_test.zig @@ -45,7 +45,7 @@ fn benchmarkCodepointCount(buf: []const u8) !ResultCount { } pub fn main() !void { - const stdout = std.io.getStdOut().outStream(); + const stdout = std.io.getStdOut().writer(); const args = try std.process.argsAlloc(std.heap.page_allocator); diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index 060dd83caf..f0849f9a03 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -519,29 +519,29 @@ pub const CrossTarget = struct { var result = std.ArrayList(u8).init(allocator); defer result.deinit(); - try result.outStream().print("{s}-{s}", .{ arch_name, os_name }); + try result.writer().print("{s}-{s}", .{ arch_name, os_name }); // The zig target syntax does not allow specifying a max os version with no min, so // if either are present, we need the min. if (self.os_version_min != null or self.os_version_max != null) { switch (self.getOsVersionMin()) { .none => {}, - .semver => |v| try result.outStream().print(".{}", .{v}), - .windows => |v| try result.outStream().print("{s}", .{v}), + .semver => |v| try result.writer().print(".{}", .{v}), + .windows => |v| try result.writer().print("{s}", .{v}), } } if (self.os_version_max) |max| { switch (max) { .none => {}, - .semver => |v| try result.outStream().print("...{}", .{v}), - .windows => |v| try result.outStream().print("..{s}", .{v}), + .semver => |v| try result.writer().print("...{}", .{v}), + .windows => |v| try result.writer().print("..{s}", .{v}), } } if (self.glibc_version) |v| { - try result.outStream().print("-{s}.{}", .{ @tagName(self.getAbi()), v }); + try result.writer().print("-{s}.{}", .{ @tagName(self.getAbi()), v }); } else if (self.abi) |abi| { - try result.outStream().print("-{s}", .{@tagName(abi)}); + try result.writer().print("-{s}", .{@tagName(abi)}); } return result.toOwnedSlice(); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 63d04f9a80..d7cc1208a2 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -3734,7 +3734,7 @@ const maxInt = std.math.maxInt; var fixed_buffer_mem: [100 * 1024]u8 = undefined; fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *bool) ![]u8 { - const stderr = io.getStdErr().outStream(); + const stderr = io.getStdErr().writer(); const tree = try std.zig.parse(allocator, source); defer tree.deinit(); @@ -3767,8 +3767,8 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b var buffer = std.ArrayList(u8).init(allocator); errdefer buffer.deinit(); - const outStream = buffer.outStream(); - anything_changed.* = try std.zig.render(allocator, outStream, tree); + const writer = buffer.writer(); + anything_changed.* = try std.zig.render(allocator, writer, tree); return buffer.toOwnedSlice(); } fn testTransform(source: []const u8, expected_source: []const u8) !void { diff --git a/lib/std/zig/perf_test.zig b/lib/std/zig/perf_test.zig index e1c58c1469..b111170902 100644 --- a/lib/std/zig/perf_test.zig +++ b/lib/std/zig/perf_test.zig @@ -29,7 +29,7 @@ pub fn main() !void { const mb_per_sec = bytes_per_sec / (1024 * 1024); var stdout_file = std.io.getStdOut(); - const stdout = stdout_file.outStream(); + const stdout = stdout_file.writer(); try stdout.print("{:.3} MiB/s, {} KiB used \n", .{ mb_per_sec, memory_used / 1024 }); } diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 6931b19250..b882d71cfd 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -790,7 +790,7 @@ fn renderExpression( const section_exprs = row_exprs[0..section_end]; // Null stream for counting the printed length of each expression - var line_find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream); + var line_find_stream = std.io.findByteOutStream('\n', std.io.null_writer); var counting_stream = std.io.countingOutStream(line_find_stream.writer()); var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer()); @@ -954,7 +954,7 @@ fn renderExpression( const expr_outputs_one_line = blk: { // render field expressions until a LF is found for (field_inits) |field_init| { - var find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream); + var find_stream = std.io.findByteOutStream('\n', std.io.null_writer); var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, find_stream.writer()); try renderExpression(allocator, &auto_indenting_stream, tree, field_init, Space.None); diff --git a/src/Cache.zig b/src/Cache.zig index 9f8beaabc7..f5ffb34dbe 100644 --- a/src/Cache.zig +++ b/src/Cache.zig @@ -285,7 +285,7 @@ pub const Manifest = struct { }; } - const file_contents = try self.manifest_file.?.inStream().readAllAlloc(self.cache.gpa, manifest_file_size_max); + const file_contents = try self.manifest_file.?.reader().readAllAlloc(self.cache.gpa, manifest_file_size_max); defer self.cache.gpa.free(file_contents); const input_file_count = self.files.items.len; diff --git a/src/Compilation.zig b/src/Compilation.zig index 95c9435aa7..a81926bf19 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1820,7 +1820,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { var out_zig_file = try o_dir.createFile(cimport_zig_basename, .{}); defer out_zig_file.close(); - var bos = std.io.bufferedOutStream(out_zig_file.writer()); + var bos = std.io.bufferedWriter(out_zig_file.writer()); _ = try std.zig.render(comp.gpa, bos.writer(), tree); try bos.flush(); @@ -2750,7 +2750,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 switch (target.os.getVersionRange()) { .none => try buffer.appendSlice(" .none = {} }\n"), - .semver => |semver| try buffer.outStream().print( + .semver => |semver| try buffer.writer().print( \\ .semver = .{{ \\ .min = .{{ \\ .major = {}, @@ -2773,7 +2773,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 semver.max.minor, semver.max.patch, }), - .linux => |linux| try buffer.outStream().print( + .linux => |linux| try buffer.writer().print( \\ .linux = .{{ \\ .range = .{{ \\ .min = .{{ @@ -2807,7 +2807,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 linux.glibc.minor, linux.glibc.patch, }), - .windows => |windows| try buffer.outStream().print( + .windows => |windows| try buffer.writer().print( \\ .windows = .{{ \\ .min = {s}, \\ .max = {s}, diff --git a/src/DepTokenizer.zig b/src/DepTokenizer.zig index 0fe1310cd8..b246a22bd0 100644 --- a/src/DepTokenizer.zig +++ b/src/DepTokenizer.zig @@ -910,7 +910,7 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void { }, else => { try buffer.appendSlice("ERROR: "); - try token.printError(buffer.outStream()); + try token.printError(buffer.writer()); break; }, } diff --git a/src/Module.zig b/src/Module.zig index 603c97ffee..9707e79e2a 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1649,7 +1649,7 @@ pub fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { var msg = std.ArrayList(u8).init(self.gpa); defer msg.deinit(); - try parse_err.render(tree.token_ids, msg.outStream()); + try parse_err.render(tree.token_ids, msg.writer()); const err_msg = try self.gpa.create(Compilation.ErrorMsg); err_msg.* = .{ .msg = msg.toOwnedSlice(), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index df97f7e0ef..9b35c42c6e 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -200,7 +200,7 @@ pub const LLVMIRModule = struct { if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message)) { defer llvm.disposeMessage(error_message); - const stderr = std.io.getStdErr().outStream(); + const stderr = std.io.getStdErr().writer(); try stderr.print( \\Zig is expecting LLVM to understand this target: '{s}' \\However LLVM responded with: "{s}" @@ -268,7 +268,7 @@ pub const LLVMIRModule = struct { const dump = self.llvm_module.printToString(); defer llvm.disposeMessage(dump); - const stderr = std.io.getStdErr().outStream(); + const stderr = std.io.getStdErr().writer(); try stderr.writeAll(std.mem.spanZ(dump)); } @@ -278,7 +278,7 @@ pub const LLVMIRModule = struct { defer llvm.disposeMessage(error_message); if (self.llvm_module.verify(.ReturnStatus, &error_message)) { - const stderr = std.io.getStdErr().outStream(); + const stderr = std.io.getStdErr().writer(); try stderr.print("broken LLVM module found: {s}\nThis is a bug in the Zig compiler.", .{error_message}); return error.BrokenLLVMModule; } @@ -296,7 +296,7 @@ pub const LLVMIRModule = struct { )) { defer llvm.disposeMessage(error_message); - const stderr = std.io.getStdErr().outStream(); + const stderr = std.io.getStdErr().writer(); try stderr.print("LLVM failed to emit file: {s}\n", .{error_message}); return error.FailedToEmit; } diff --git a/src/libc_installation.zig b/src/libc_installation.zig index bc317869e8..05c85578ea 100644 --- a/src/libc_installation.zig +++ b/src/libc_installation.zig @@ -338,7 +338,7 @@ pub const LibCInstallation = struct { for (searches) |search| { result_buf.shrinkAndFree(0); - try result_buf.outStream().print("{s}\\Include\\{s}\\ucrt", .{ search.path, search.version }); + try result_buf.writer().print("{s}\\Include\\{s}\\ucrt", .{ search.path, search.version }); var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, @@ -384,7 +384,7 @@ pub const LibCInstallation = struct { for (searches) |search| { result_buf.shrinkAndFree(0); - try result_buf.outStream().print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ search.path, search.version, arch_sub_dir }); + try result_buf.writer().print("{s}\\Lib\\{s}\\ucrt\\{s}", .{ search.path, search.version, arch_sub_dir }); var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { error.FileNotFound, @@ -438,7 +438,7 @@ pub const LibCInstallation = struct { for (searches) |search| { result_buf.shrinkAndFree(0); - const stream = result_buf.outStream(); + const stream = result_buf.writer(); try stream.print("{s}\\Lib\\{s}\\um\\{s}", .{ search.path, search.version, arch_sub_dir }); var dir = fs.cwd().openDir(result_buf.items, .{}) catch |err| switch (err) { diff --git a/src/main.zig b/src/main.zig index cd89f269d0..7829901acc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -196,7 +196,7 @@ pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v return cmdInit(gpa, arena, cmd_args, .Lib); } else if (mem.eql(u8, cmd, "targets")) { const info = try detectNativeTargetInfo(arena, .{}); - const stdout = io.getStdOut().outStream(); + const stdout = io.getStdOut().writer(); return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); } else if (mem.eql(u8, cmd, "version")) { try std.io.getStdOut().writeAll(build_options.version ++ "\n"); @@ -1944,8 +1944,8 @@ fn buildOutputType( } } - const stdin = std.io.getStdIn().inStream(); - const stderr = std.io.getStdErr().outStream(); + const stdin = std.io.getStdIn().reader(); + const stderr = std.io.getStdErr().writer(); var repl_buf: [1024]u8 = undefined; while (watch) { @@ -2114,9 +2114,9 @@ fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !voi var zig_file = try o_dir.createFile(translated_zig_basename, .{}); defer zig_file.close(); - var bos = io.bufferedOutStream(zig_file.writer()); - _ = try std.zig.render(comp.gpa, bos.writer(), tree); - try bos.flush(); + var bw = io.bufferedWriter(zig_file.writer()); + _ = try std.zig.render(comp.gpa, bw.writer(), tree); + try bw.flush(); man.writeManifest() catch |err| warn("failed to write cache manifest: {s}", .{@errorName(err)}); @@ -2187,9 +2187,9 @@ pub fn cmdLibC(gpa: *Allocator, args: []const []const u8) !void { }; defer libc.deinit(gpa); - var bos = io.bufferedOutStream(io.getStdOut().writer()); - try libc.render(bos.writer()); - try bos.flush(); + var bw = io.bufferedWriter(io.getStdOut().writer()); + try libc.render(bw.writer()); + try bw.flush(); } } @@ -2570,7 +2570,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { - const stdout = io.getStdOut().outStream(); + const stdout = io.getStdOut().writer(); try stdout.writeAll(usage_fmt); return cleanExit(); } else if (mem.eql(u8, arg, "--color")) { @@ -2600,7 +2600,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { fatal("cannot use --stdin with positional arguments", .{}); } - const stdin = io.getStdIn().inStream(); + const stdin = io.getStdIn().reader(); const source_code = try stdin.readAllAlloc(gpa, max_src_size); defer gpa.free(source_code); @@ -2617,14 +2617,14 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { process.exit(1); } if (check_flag) { - const anything_changed = try std.zig.render(gpa, io.null_out_stream, tree); + const anything_changed = try std.zig.render(gpa, io.null_writer, tree); const code = if (anything_changed) @as(u8, 1) else @as(u8, 0); process.exit(code); } - var bos = io.bufferedOutStream(io.getStdOut().writer()); - _ = try std.zig.render(gpa, bos.writer(), tree); - try bos.flush(); + var bw = io.bufferedWriter(io.getStdOut().writer()); + _ = try std.zig.render(gpa, bw.writer(), tree); + try bw.flush(); return; } @@ -2774,7 +2774,7 @@ fn fmtPathFile( } if (check_mode) { - const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree); + const anything_changed = try std.zig.render(fmt.gpa, io.null_writer, tree); if (anything_changed) { const stdout = io.getStdOut().writer(); try stdout.print("{s}\n", .{file_path}); @@ -2823,11 +2823,11 @@ fn printErrMsgToFile( var text_buf = std.ArrayList(u8).init(gpa); defer text_buf.deinit(); - const out_stream = text_buf.outStream(); - try parse_error.render(tree.token_ids, out_stream); + const writer = text_buf.writer(); + try parse_error.render(tree.token_ids, writer); const text = text_buf.items; - const stream = file.outStream(); + const stream = file.writer(); try stream.print("{s}:{d}:{d}: error: {s}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text }); if (!color_on) return; diff --git a/src/print_env.zig b/src/print_env.zig index bcf4a983ab..d62e1f62fd 100644 --- a/src/print_env.zig +++ b/src/print_env.zig @@ -20,10 +20,10 @@ pub fn cmdEnv(gpa: *Allocator, args: []const []const u8, stdout: std.fs.File.Wri const global_cache_dir = try introspect.resolveGlobalCacheDir(gpa); defer gpa.free(global_cache_dir); - var bos = std.io.bufferedOutStream(stdout); - const bos_stream = bos.outStream(); + var bw = std.io.bufferedWriter(stdout); + const w = bw.writer(); - var jws = std.json.WriteStream(@TypeOf(bos_stream), 6).init(bos_stream); + var jws = std.json.WriteStream(@TypeOf(w), 6).init(w); try jws.beginObject(); try jws.objectField("zig_exe"); @@ -42,6 +42,6 @@ pub fn cmdEnv(gpa: *Allocator, args: []const []const u8, stdout: std.fs.File.Wri try jws.emitString(build_options.version); try jws.endObject(); - try bos_stream.writeByte('\n'); - try bos.flush(); + try w.writeByte('\n'); + try bw.flush(); } diff --git a/src/print_targets.zig b/src/print_targets.zig index cf55eee516..e24a2294a1 100644 --- a/src/print_targets.zig +++ b/src/print_targets.zig @@ -26,9 +26,9 @@ pub fn cmdTargets( const glibc_abi = try glibc.loadMetaData(allocator, zig_lib_directory.handle); defer glibc_abi.destroy(allocator); - var bos = io.bufferedOutStream(stdout); - const bos_stream = bos.outStream(); - var jws = std.json.WriteStream(@TypeOf(bos_stream), 6).init(bos_stream); + var bw = io.bufferedWriter(stdout); + const w = bw.writer(); + var jws = std.json.WriteStream(@TypeOf(w), 6).init(w); try jws.beginObject(); @@ -156,6 +156,6 @@ pub fn cmdTargets( try jws.endObject(); - try bos_stream.writeByte('\n'); - return bos.flush(); + try w.writeByte('\n'); + return bw.flush(); } diff --git a/src/test.zig b/src/test.zig index 682fb5078f..2553e57ff4 100644 --- a/src/test.zig +++ b/src/test.zig @@ -738,7 +738,7 @@ pub const TestContext = struct { write_node.activate(); var out_zir = std.ArrayList(u8).init(allocator); defer out_zir.deinit(); - try new_zir_module.writeToStream(allocator, out_zir.outStream()); + try new_zir_module.writeToStream(allocator, out_zir.writer()); write_node.end(); var test_node = update_node.start("assert", 0); diff --git a/src/translate_c.zig b/src/translate_c.zig index 9369c6d4b8..8df7963267 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -5268,7 +5268,7 @@ fn appendTokenFmt(c: *Context, token_id: Token.Id, comptime format: []const u8, try c.token_locs.ensureCapacity(c.gpa, c.token_locs.items.len + 1); const start_index = c.source_buffer.items.len; - try c.source_buffer.outStream().print(format ++ " ", args); + try c.source_buffer.writer().print(format ++ " ", args); c.token_ids.appendAssumeCapacity(token_id); c.token_locs.appendAssumeCapacity(.{ diff --git a/src/zir.zig b/src/zir.zig index 63850d67db..b3d004f1dc 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1116,7 +1116,7 @@ pub const Module = struct { /// This is a debugging utility for rendering the tree to stderr. pub fn dump(self: Module) void { - self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {}; + self.writeToStream(std.heap.page_allocator, std.io.getStdErr().writer()) catch {}; } const DeclAndIndex = struct { @@ -3254,7 +3254,7 @@ pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8 try write.inst_table.ensureCapacity(@intCast(u32, instructions.len)); - const stderr = std.io.getStdErr().outStream(); + const stderr = std.io.getStdErr().writer(); try stderr.print("{s} {s} {{ // unanalyzed\n", .{ kind, decl_name }); for (instructions) |inst| { diff --git a/test/compare_output.zig b/test/compare_output.zig index 9dc80f202d..a6e9835ea1 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -22,7 +22,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ \\pub fn main() void { \\ privateFunction(); - \\ const stdout = getStdOut().outStream(); + \\ const stdout = getStdOut().writer(); \\ stdout.print("OK 2\n", .{}) catch unreachable; \\} \\ @@ -37,7 +37,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\// purposefully conflicting function with main.zig \\// but it's private so it should be OK \\fn privateFunction() void { - \\ const stdout = getStdOut().outStream(); + \\ const stdout = getStdOut().writer(); \\ stdout.print("OK 1\n", .{}) catch unreachable; \\} \\ @@ -63,7 +63,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { tc.addSourceFile("foo.zig", \\usingnamespace @import("std").io; \\pub fn foo_function() void { - \\ const stdout = getStdOut().outStream(); + \\ const stdout = getStdOut().writer(); \\ stdout.print("OK\n", .{}) catch unreachable; \\} ); @@ -74,7 +74,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ \\pub fn bar_function() void { \\ if (foo_function()) { - \\ const stdout = getStdOut().outStream(); + \\ const stdout = getStdOut().writer(); \\ stdout.print("OK\n", .{}) catch unreachable; \\ } \\} @@ -106,7 +106,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub const a_text = "OK\n"; \\ \\pub fn ok() void { - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ stdout.print(b_text, .{}) catch unreachable; \\} ); @@ -124,7 +124,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\const io = @import("std").io; \\ \\pub fn main() void { - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ stdout.print("Hello, world!\n{d:4} {x:3} {c}\n", .{@as(u32, 12), @as(u16, 0x12), @as(u8, 'a')}) catch unreachable; \\} , "Hello, world!\n 12 12 a\n"); @@ -267,7 +267,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ var x_local : i32 = print_ok(x); \\} \\fn print_ok(val: @TypeOf(x)) @TypeOf(foo) { - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ stdout.print("OK\n", .{}) catch unreachable; \\ return 0; \\} @@ -349,7 +349,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub fn main() void { \\ const bar = Bar {.field2 = 13,}; \\ const foo = Foo {.field1 = bar,}; - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ if (!foo.method()) { \\ stdout.print("BAD\n", .{}) catch unreachable; \\ } @@ -363,7 +363,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { cases.add("defer with only fallthrough", \\const io = @import("std").io; \\pub fn main() void { - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ stdout.print("before\n", .{}) catch unreachable; \\ defer stdout.print("defer1\n", .{}) catch unreachable; \\ defer stdout.print("defer2\n", .{}) catch unreachable; @@ -376,7 +376,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\const io = @import("std").io; \\const os = @import("std").os; \\pub fn main() void { - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ stdout.print("before\n", .{}) catch unreachable; \\ defer stdout.print("defer1\n", .{}) catch unreachable; \\ defer stdout.print("defer2\n", .{}) catch unreachable; @@ -393,7 +393,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ do_test() catch return; \\} \\fn do_test() !void { - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ stdout.print("before\n", .{}) catch unreachable; \\ defer stdout.print("defer1\n", .{}) catch unreachable; \\ errdefer stdout.print("deferErr\n", .{}) catch unreachable; @@ -412,7 +412,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ do_test() catch return; \\} \\fn do_test() !void { - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ stdout.print("before\n", .{}) catch unreachable; \\ defer stdout.print("defer1\n", .{}) catch unreachable; \\ errdefer stdout.print("deferErr\n", .{}) catch unreachable; @@ -429,7 +429,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\const io = @import("std").io; \\ \\pub fn main() void { - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ stdout.print(foo_txt, .{}) catch unreachable; \\} , "1234\nabcd\n"); @@ -448,7 +448,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ \\pub fn main() !void { \\ var args_it = std.process.args(); - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ var index: usize = 0; \\ _ = args_it.skip(); \\ while (args_it.next(allocator)) |arg_or_err| : (index += 1) { @@ -487,7 +487,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ \\pub fn main() !void { \\ var args_it = std.process.args(); - \\ const stdout = io.getStdOut().outStream(); + \\ const stdout = io.getStdOut().writer(); \\ var index: usize = 0; \\ _ = args_it.skip(); \\ while (args_it.next(allocator)) |arg_or_err| : (index += 1) { diff --git a/test/stage1/behavior/bugs/5487.zig b/test/stage1/behavior/bugs/5487.zig index 02fa677a44..bf530a8a02 100644 --- a/test/stage1/behavior/bugs/5487.zig +++ b/test/stage1/behavior/bugs/5487.zig @@ -3,10 +3,10 @@ const io = @import("std").io; pub fn write(_: void, bytes: []const u8) !usize { return 0; } -pub fn outStream() io.OutStream(void, @typeInfo(@typeInfo(@TypeOf(write)).Fn.return_type.?).ErrorUnion.error_set, write) { - return io.OutStream(void, @typeInfo(@typeInfo(@TypeOf(write)).Fn.return_type.?).ErrorUnion.error_set, write){ .context = {} }; +pub fn writer() io.Writer(void, @typeInfo(@typeInfo(@TypeOf(write)).Fn.return_type.?).ErrorUnion.error_set, write) { + return io.Writer(void, @typeInfo(@typeInfo(@TypeOf(write)).Fn.return_type.?).ErrorUnion.error_set, write){ .context = {} }; } test "crash" { - _ = io.multiOutStream(.{outStream()}); + _ = io.multiWriter(.{writer()}); } diff --git a/test/tests.zig b/test/tests.zig index 5ee381e5c2..ec6d9e1df8 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -652,9 +652,9 @@ pub const StackTracesContext = struct { } child.spawn() catch |err| debug.panic("Unable to spawn {s}: {s}\n", .{ full_exe_path, @errorName(err) }); - const stdout = child.stdout.?.inStream().readAllAlloc(b.allocator, max_stdout_size) catch unreachable; + const stdout = child.stdout.?.reader().readAllAlloc(b.allocator, max_stdout_size) catch unreachable; defer b.allocator.free(stdout); - const stderrFull = child.stderr.?.inStream().readAllAlloc(b.allocator, max_stdout_size) catch unreachable; + const stderrFull = child.stderr.?.reader().readAllAlloc(b.allocator, max_stdout_size) catch unreachable; defer b.allocator.free(stderrFull); var stderr = stderrFull; @@ -875,8 +875,8 @@ pub const CompileErrorContext = struct { var stdout_buf = ArrayList(u8).init(b.allocator); var stderr_buf = ArrayList(u8).init(b.allocator); - child.stdout.?.inStream().readAllArrayList(&stdout_buf, max_stdout_size) catch unreachable; - child.stderr.?.inStream().readAllArrayList(&stderr_buf, max_stdout_size) catch unreachable; + child.stdout.?.reader().readAllArrayList(&stdout_buf, max_stdout_size) catch unreachable; + child.stderr.?.reader().readAllArrayList(&stderr_buf, max_stdout_size) catch unreachable; const term = child.wait() catch |err| { debug.panic("Unable to spawn {s}: {s}\n", .{ zig_args.items[0], @errorName(err) }); diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index fd64bb062a..ad380e0139 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -388,8 +388,8 @@ pub fn main() anyerror!void { // "W" and "Wl,". So we sort this list in order of descending priority. std.sort.sort(*json.ObjectMap, all_objects.items, {}, objectLessThan); - var stdout_bos = std.io.bufferedOutStream(std.io.getStdOut().outStream()); - const stdout = stdout_bos.outStream(); + var buffered_stdout = std.io.bufferedWriter(std.io.getStdOut().writer()); + const stdout = buffered_stdout.writer(); try stdout.writeAll( \\// This file is generated by tools/update_clang_options.zig. \\// zig fmt: off @@ -469,7 +469,7 @@ pub fn main() anyerror!void { \\ ); - try stdout_bos.flush(); + try buffered_stdout.flush(); } // TODO we should be able to import clang_options.zig but currently this is problematic because it will @@ -611,7 +611,7 @@ fn objectLessThan(context: void, a: *json.ObjectMap, b: *json.ObjectMap) bool { } fn usageAndExit(file: fs.File, arg0: []const u8, code: u8) noreturn { - file.outStream().print( + file.writer().print( \\Usage: {} /path/to/llvm-tblgen /path/to/git/llvm/llvm-project \\Alternative Usage: zig run /path/to/git/zig/tools/update_clang_options.zig -- /path/to/llvm-tblgen /path/to/git/llvm/llvm-project \\ diff --git a/tools/update_glibc.zig b/tools/update_glibc.zig index 03b746f0ab..1e23bf0ff8 100644 --- a/tools/update_glibc.zig +++ b/tools/update_glibc.zig @@ -239,7 +239,7 @@ pub fn main() !void { const vers_txt_path = try fs.path.join(allocator, &[_][]const u8{ glibc_out_dir, "vers.txt" }); const vers_txt_file = try fs.cwd().createFile(vers_txt_path, .{}); defer vers_txt_file.close(); - var buffered = std.io.bufferedOutStream(vers_txt_file.writer()); + var buffered = std.io.bufferedWriter(vers_txt_file.writer()); const vers_txt = buffered.writer(); for (global_ver_list) |name, i| { _ = global_ver_set.put(name, i) catch unreachable; @@ -251,7 +251,7 @@ pub fn main() !void { const fns_txt_path = try fs.path.join(allocator, &[_][]const u8{ glibc_out_dir, "fns.txt" }); const fns_txt_file = try fs.cwd().createFile(fns_txt_path, .{}); defer fns_txt_file.close(); - var buffered = std.io.bufferedOutStream(fns_txt_file.writer()); + var buffered = std.io.bufferedWriter(fns_txt_file.writer()); const fns_txt = buffered.writer(); for (global_fn_list) |name, i| { const entry = global_fn_set.getEntry(name).?; @@ -282,7 +282,7 @@ pub fn main() !void { const abilist_txt_path = try fs.path.join(allocator, &[_][]const u8{ glibc_out_dir, "abi.txt" }); const abilist_txt_file = try fs.cwd().createFile(abilist_txt_path, .{}); defer abilist_txt_file.close(); - var buffered = std.io.bufferedOutStream(abilist_txt_file.writer()); + var buffered = std.io.bufferedWriter(abilist_txt_file.writer()); const abilist_txt = buffered.writer(); // first iterate over the abi lists -- cgit v1.2.3 From 31802c6c68a98bdbe34766d3cfdaf65b782851da Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sun, 3 Jan 2021 13:49:51 -0700 Subject: remove z/Z format specifiers Zig's format system is flexible enough to add custom formatters. This PR removes the new z/Z format specifiers that were added for printing Zig identifiers and replaces them with custom formatters. --- lib/std/build.zig | 32 +++++++++---------- lib/std/fmt.zig | 89 +++++++++++++++++------------------------------------ lib/std/fs/wasi.zig | 2 +- lib/std/zig.zig | 2 ++ lib/std/zig/fmt.zig | 71 ++++++++++++++++++++++++++++++++++++++++++ src/Compilation.zig | 44 +++++++++++++------------- src/codegen/c.zig | 4 +-- src/translate_c.zig | 7 +++-- src/value.zig | 4 +-- src/zir.zig | 8 ++--- 10 files changed, 153 insertions(+), 110 deletions(-) create mode 100644 lib/std/zig/fmt.zig (limited to 'src/codegen') diff --git a/lib/std/build.zig b/lib/std/build.zig index cb4cb229e3..5fd64cad0b 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1852,25 +1852,25 @@ pub const LibExeObjStep = struct { const out = self.build_options_contents.writer(); switch (T) { []const []const u8 => { - out.print("pub const {z}: []const []const u8 = &[_][]const u8{{\n", .{name}) catch unreachable; + out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}) catch unreachable; for (value) |slice| { - out.print(" \"{Z}\",\n", .{slice}) catch unreachable; + out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}) catch unreachable; } out.writeAll("};\n") catch unreachable; return; }, [:0]const u8 => { - out.print("pub const {z}: [:0]const u8 = \"{Z}\";\n", .{ name, value }) catch unreachable; + out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; return; }, []const u8 => { - out.print("pub const {z}: []const u8 = \"{Z}\";\n", .{ name, value }) catch unreachable; + out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; return; }, ?[]const u8 => { - out.print("pub const {z}: ?[]const u8 = ", .{name}) catch unreachable; + out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; if (value) |payload| { - out.print("\"{Z}\";\n", .{payload}) catch unreachable; + out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; } else { out.writeAll("null;\n") catch unreachable; } @@ -1878,14 +1878,14 @@ pub const LibExeObjStep = struct { }, std.builtin.Version => { out.print( - \\pub const {z}: @import("builtin").Version = .{{ + \\pub const {}: @import("builtin").Version = .{{ \\ .major = {d}, \\ .minor = {d}, \\ .patch = {d}, \\}}; \\ , .{ - name, + std.zig.fmtId(name), value.major, value.minor, @@ -1894,23 +1894,23 @@ pub const LibExeObjStep = struct { }, std.SemanticVersion => { out.print( - \\pub const {z}: @import("std").SemanticVersion = .{{ + \\pub const {}: @import("std").SemanticVersion = .{{ \\ .major = {d}, \\ .minor = {d}, \\ .patch = {d}, \\ , .{ - name, + std.zig.fmtId(name), value.major, value.minor, value.patch, }) catch unreachable; if (value.pre) |some| { - out.print(" .pre = \"{Z}\",\n", .{some}) catch unreachable; + out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; } if (value.build) |some| { - out.print(" .build = \"{Z}\",\n", .{some}) catch unreachable; + out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; } out.writeAll("};\n") catch unreachable; return; @@ -1919,15 +1919,15 @@ pub const LibExeObjStep = struct { } switch (@typeInfo(T)) { .Enum => |enum_info| { - out.print("pub const {z} = enum {{\n", .{@typeName(T)}) catch unreachable; + out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}) catch unreachable; inline for (enum_info.fields) |field| { - out.print(" {z},\n", .{field.name}) catch unreachable; + out.print(" {},\n", .{std.zig.fmtId(field.name)}) catch unreachable; } out.writeAll("};\n") catch unreachable; }, else => {}, } - out.print("pub const {z}: {s} = {};\n", .{ name, @typeName(T), value }) catch unreachable; + out.print("pub const {}: {s} = {};\n", .{ std.zig.fmtId(name), @typeName(T), value }) catch unreachable; } /// The value is the path in the cache dir. @@ -2157,7 +2157,7 @@ pub const LibExeObjStep = struct { // Render build artifact options at the last minute, now that the path is known. for (self.build_options_artifact_args.items) |item| { const out = self.build_options_contents.writer(); - out.print("pub const {s}: []const u8 = \"{Z}\";\n", .{ item.name, item.artifact.getOutputPath() }) catch unreachable; + out.print("pub const {s}: []const u8 = \"{}\";\n", .{ item.name, std.zig.fmtEscapes(item.artifact.getOutputPath()) }) catch unreachable; } const build_options_file = try fs.path.join( diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index bc9ac92283..d8b81eb264 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -715,9 +715,9 @@ pub fn formatText( } return; } else if (comptime std.mem.eql(u8, fmt, "z")) { - return formatZigIdentifier(bytes, options, writer); + @compileError("specifier 'z' has been deprecated, wrap your argument in std.zig.fmtId instead"); } else if (comptime std.mem.eql(u8, fmt, "Z")) { - return formatZigEscapes(bytes, options, writer); + @compileError("specifier 'Z' has been deprecated, wrap your argument in std.zig.fmtEscapes instead"); } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } @@ -782,52 +782,6 @@ pub fn formatBuf( } } -/// Print the string as a Zig identifier escaping it with @"" syntax if needed. -pub fn formatZigIdentifier( - bytes: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - if (isValidZigIdentifier(bytes)) { - return writer.writeAll(bytes); - } - try writer.writeAll("@\""); - try formatZigEscapes(bytes, options, writer); - try writer.writeByte('"'); -} - -fn isValidZigIdentifier(bytes: []const u8) bool { - for (bytes) |c, i| { - switch (c) { - '_', 'a'...'z', 'A'...'Z' => {}, - '0'...'9' => if (i == 0) return false, - else => return false, - } - } - return std.zig.Token.getKeyword(bytes) == null; -} - -pub fn formatZigEscapes( - bytes: []const u8, - options: FormatOptions, - writer: anytype, -) !void { - for (bytes) |byte| switch (byte) { - '\n' => try writer.writeAll("\\n"), - '\r' => try writer.writeAll("\\r"), - '\t' => try writer.writeAll("\\t"), - '\\' => try writer.writeAll("\\\\"), - '"' => try writer.writeAll("\\\""), - '\'' => try writer.writeAll("\\'"), - ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte), - // Use hex escapes for rest any unprintable characters. - else => { - try writer.writeAll("\\x"); - try formatInt(byte, 16, false, .{ .width = 2, .fill = '0' }, writer); - }, - }; -} - /// Print a float in scientific notation to the specified precision. Null uses full precision. /// It should be the case that every full precision, printed value can be re-parsed back to the /// same type unambiguously. @@ -1173,6 +1127,32 @@ pub const ParseIntError = error{ InvalidCharacter, }; +/// Creates a Formatter type from a format function. Wrapping data in Formatter(func) causes +/// the data to be formatted using the given function `func`. `func` must be of the following +/// form: +/// +/// fn formatExample( +/// data: T, +/// comptime fmt: []const u8, +/// options: std.fmt.FormatOptions, +/// writer: anytype, +/// ) !void; +/// +pub fn Formatter(comptime format_fn: anytype) type { + const Data = @typeInfo(@TypeOf(format_fn)).Fn.args[0].arg_type.?; + return struct { + data: Data, + pub fn format( + self: @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + try format_fn(self.data, fmt, options, writer); + } + }; +} + /// Parses the string `buf` as signed or unsigned representation in the /// specified radix of an integral value of type `T`. /// @@ -1608,17 +1588,6 @@ test "escape non-printable" { try testFmt("ab\\xFFc", "{E}", .{"ab\xffc"}); } -test "escape invalid identifiers" { - try testFmt("@\"while\"", "{z}", .{"while"}); - try testFmt("hello", "{z}", .{"hello"}); - try testFmt("@\"11\\\"23\"", "{z}", .{"11\"23"}); - try testFmt("@\"11\\x0f23\"", "{z}", .{"11\x0F23"}); - try testFmt("\\x0f", "{Z}", .{0x0f}); - try testFmt( - \\" \\ hi \x07 \x11 \" derp \'" - , "\"{Z}\"", .{" \\ hi \x07 \x11 \" derp '"}); -} - test "pointer" { { const value = @intToPtr(*align(1) i32, 0xdeadbeef); @@ -1898,7 +1867,7 @@ test "bytes.hex" { try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); } -fn testFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { +pub fn testFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { var buf: [100]u8 = undefined; const result = try bufPrint(buf[0..], template, args); if (mem.eql(u8, result, expected)) return; diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index 900adc5e2d..cf5431f994 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -38,7 +38,7 @@ pub const PreopenType = union(PreopenTypeTag) { pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void { try out_stream.print("PreopenType{{ ", .{}); switch (self) { - PreopenType.Dir => |path| try out_stream.print(".Dir = '{z}'", .{path}), + PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{std.zig.fmtId(path)}), } return out_stream.print(" }}", .{}); } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 5d95031e02..c39eb6b05f 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -8,6 +8,8 @@ const tokenizer = @import("zig/tokenizer.zig"); pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; +pub const fmtId = @import("zig/fmt.zig").fmtId; +pub const fmtEscapes = @import("zig/fmt.zig").fmtEscapes; pub const parse = @import("zig/parse.zig").parse; pub const parseStringLiteral = @import("zig/string_literal.zig").parse; pub const render = @import("zig/render.zig").render; diff --git a/lib/std/zig/fmt.zig b/lib/std/zig/fmt.zig new file mode 100644 index 0000000000..d852f2b49d --- /dev/null +++ b/lib/std/zig/fmt.zig @@ -0,0 +1,71 @@ +const std = @import("std"); +const mem = std.mem; + +/// Print the string as a Zig identifier escaping it with @"" syntax if needed. +pub fn formatId( + bytes: []const u8, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + if (isValidId(bytes)) { + return writer.writeAll(bytes); + } + try writer.writeAll("@\""); + try formatEscapes(bytes, fmt, options, writer); + try writer.writeByte('"'); +} + +/// Return a Formatter for a Zig identifier +pub fn fmtId(bytes: []const u8) std.fmt.Formatter(formatId) { + return .{ .data = bytes }; +} + +pub fn isValidId(bytes: []const u8) bool { + for (bytes) |c, i| { + switch (c) { + '_', 'a'...'z', 'A'...'Z' => {}, + '0'...'9' => if (i == 0) return false, + else => return false, + } + } + return std.zig.Token.getKeyword(bytes) == null; +} + +pub fn formatEscapes( + bytes: []const u8, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + for (bytes) |byte| switch (byte) { + '\n' => try writer.writeAll("\\n"), + '\r' => try writer.writeAll("\\r"), + '\t' => try writer.writeAll("\\t"), + '\\' => try writer.writeAll("\\\\"), + '"' => try writer.writeAll("\\\""), + '\'' => try writer.writeAll("\\'"), + ' ', '!', '#'...'&', '('...'[', ']'...'~' => try writer.writeByte(byte), + // Use hex escapes for rest any unprintable characters. + else => { + try writer.writeAll("\\x"); + try std.fmt.formatInt(byte, 16, false, .{ .width = 2, .fill = '0' }, writer); + }, + }; +} + +/// Return a Formatter for Zig Escapes +pub fn fmtEscapes(bytes: []const u8) std.fmt.Formatter(formatEscapes) { + return .{ .data = bytes }; +} + +test "escape invalid identifiers" { + try std.fmt.testFmt("@\"while\"", "{}", .{fmtId("while")}); + try std.fmt.testFmt("hello", "{}", .{fmtId("hello")}); + try std.fmt.testFmt("@\"11\\\"23\"", "{}", .{fmtId("11\"23")}); + try std.fmt.testFmt("@\"11\\x0f23\"", "{}", .{fmtId("11\x0F23")}); + try std.fmt.testFmt("\\x0f", "{}", .{fmtEscapes("\x0f")}); + try std.fmt.testFmt( + \\" \\ hi \x07 \x11 \" derp \'" + , "\"{}\"", .{fmtEscapes(" \\ hi \x07 \x11 \" derp '")}); +} diff --git a/src/Compilation.zig b/src/Compilation.zig index a81926bf19..75b9ac7520 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2703,27 +2703,27 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\pub const arch = Target.current.cpu.arch; \\/// Deprecated \\pub const endian = Target.current.cpu.arch.endian(); - \\pub const output_mode = OutputMode.{z}; - \\pub const link_mode = LinkMode.{z}; + \\pub const output_mode = OutputMode.{}; + \\pub const link_mode = LinkMode.{}; \\pub const is_test = {}; \\pub const single_threaded = {}; - \\pub const abi = Abi.{z}; + \\pub const abi = Abi.{}; \\pub const cpu: Cpu = Cpu{{ - \\ .arch = .{z}, - \\ .model = &Target.{z}.cpu.{z}, - \\ .features = Target.{z}.featureSet(&[_]Target.{z}.Feature{{ + \\ .arch = .{}, + \\ .model = &Target.{}.cpu.{}, + \\ .features = Target.{}.featureSet(&[_]Target.{}.Feature{{ \\ , .{ - @tagName(comp.bin_file.options.output_mode), - @tagName(comp.bin_file.options.link_mode), + std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)), + std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)), comp.bin_file.options.is_test, comp.bin_file.options.single_threaded, - @tagName(target.abi), - @tagName(target.cpu.arch), - generic_arch_name, - target.cpu.model.name, - generic_arch_name, - generic_arch_name, + std.zig.fmtId(@tagName(target.abi)), + std.zig.fmtId(@tagName(target.cpu.arch)), + std.zig.fmtId(generic_arch_name), + std.zig.fmtId(target.cpu.model.name), + std.zig.fmtId(generic_arch_name), + std.zig.fmtId(generic_arch_name), }); for (target.cpu.arch.allFeaturesList()) |feature, index_usize| { @@ -2742,10 +2742,10 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\ }}), \\}}; \\pub const os = Os{{ - \\ .tag = .{z}, + \\ .tag = .{}, \\ .version_range = .{{ , - .{@tagName(target.os.tag)}, + .{std.zig.fmtId(@tagName(target.os.tag))}, ); switch (target.os.getVersionRange()) { @@ -2828,8 +2828,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 (comp.bin_file.options.skip_linker_dependencies and comp.bin_file.options.parent_compilation_link_libc); try buffer.writer().print( - \\pub const object_format = ObjectFormat.{z}; - \\pub const mode = Mode.{z}; + \\pub const object_format = ObjectFormat.{}; + \\pub const mode = Mode.{}; \\pub const link_libc = {}; \\pub const link_libcpp = {}; \\pub const have_error_return_tracing = {}; @@ -2837,11 +2837,11 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\pub const position_independent_code = {}; \\pub const position_independent_executable = {}; \\pub const strip_debug_info = {}; - \\pub const code_model = CodeModel.{z}; + \\pub const code_model = CodeModel.{}; \\ , .{ - @tagName(comp.bin_file.options.object_format), - @tagName(comp.bin_file.options.optimize_mode), + std.zig.fmtId(@tagName(comp.bin_file.options.object_format)), + std.zig.fmtId(@tagName(comp.bin_file.options.optimize_mode)), link_libc, comp.bin_file.options.link_libcpp, comp.bin_file.options.error_return_tracing, @@ -2849,7 +2849,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 comp.bin_file.options.pic, comp.bin_file.options.pie, comp.bin_file.options.strip, - @tagName(comp.bin_file.options.machine_code_model), + std.zig.fmtId(@tagName(comp.bin_file.options.machine_code_model)), }); if (comp.bin_file.options.is_test) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 3de79e2a3d..8c85f482fd 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -169,8 +169,8 @@ pub const DeclGen = struct { .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 {Z} - try writer.print("\"{Z}\"", .{bytes}); + // 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. diff --git a/src/translate_c.zig b/src/translate_c.zig index 8df7963267..baca8c12de 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2031,7 +2031,7 @@ fn transStringLiteral( const bytes_ptr = stmt.getString_bytes_begin_size(&len); const str = bytes_ptr[0..len]; - const token = try appendTokenFmt(rp.c, .StringLiteral, "\"{Z}\"", .{str}); + const token = try appendTokenFmt(rp.c, .StringLiteral, "\"{}\"", .{std.zig.fmtEscapes(str)}); const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .StringLiteral }, @@ -2944,7 +2944,8 @@ fn transCharLiteral( if (val > 255) break :blk try transCreateNodeInt(rp.c, val); } - const token = try appendTokenFmt(rp.c, .CharLiteral, "'{Z}'", .{@intCast(u8, val)}); + const val_array = [_]u8 { @intCast(u8, val) }; + const token = try appendTokenFmt(rp.c, .CharLiteral, "'{}'", .{std.zig.fmtEscapes(&val_array)}); const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ .base = .{ .tag = .CharLiteral }, @@ -5315,7 +5316,7 @@ fn isZigPrimitiveType(name: []const u8) bool { } fn appendIdentifier(c: *Context, name: []const u8) !ast.TokenIndex { - return appendTokenFmt(c, .Identifier, "{z}", .{name}); + return appendTokenFmt(c, .Identifier, "{}", .{std.zig.fmtId(name)}); } fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node { diff --git a/src/value.zig b/src/value.zig index 11c385b446..036e3b71bf 100644 --- a/src/value.zig +++ b/src/value.zig @@ -491,8 +491,8 @@ pub const Value = extern union { val = elem_ptr.array_ptr; }, .empty_array => return out_stream.writeAll(".{}"), - .enum_literal => return out_stream.print(".{z}", .{self.castTag(.enum_literal).?.data}), - .bytes => return out_stream.print("\"{Z}\"", .{self.castTag(.bytes).?.data}), + .enum_literal => return out_stream.print(".{}", .{std.zig.fmtId(self.castTag(.enum_literal).?.data)}), + .bytes => return out_stream.print("\"{}\"", .{std.zig.fmtEscapes(self.castTag(.bytes).?.data)}), .repeated => { try out_stream.writeAll("(repeated) "); val = val.castTag(.repeated).?.data; diff --git a/src/zir.zig b/src/zir.zig index b3d004f1dc..246e9dda1f 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -1308,17 +1308,17 @@ const Writer = struct { try stream.writeByte('}'); }, bool => return stream.writeByte("01"[@boolToInt(param)]), - []u8, []const u8 => return stream.print("\"{Z}\"", .{param}), + []u8, []const u8 => return stream.print("\"{}\"", .{std.zig.fmtEscapes(param)}), BigIntConst, usize => return stream.print("{}", .{param}), TypedValue => return stream.print("TypedValue{{ .ty = {}, .val = {}}}", .{ param.ty, param.val }), *IrModule.Decl => return stream.print("Decl({s})", .{param.name}), *Inst.Block => { const name = self.block_table.get(param).?; - return stream.print("\"{Z}\"", .{name}); + return stream.print("\"{}\"", .{std.zig.fmtEscapes(name)}); }, *Inst.Loop => { const name = self.loop_table.get(param).?; - return stream.print("\"{Z}\"", .{name}); + return stream.print("\"{}\"", .{std.zig.fmtEscapes(name)}); }, [][]const u8 => { try stream.writeByte('['); @@ -1326,7 +1326,7 @@ const Writer = struct { if (i != 0) { try stream.writeAll(", "); } - try stream.print("\"{Z}\"", .{str}); + try stream.print("\"{}\"", .{std.zig.fmtEscapes(str)}); } try stream.writeByte(']'); }, -- cgit v1.2.3 From 56c059077cdaf71220cb44f06902051a34ffd31d Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Fri, 8 Jan 2021 19:28:34 +0100 Subject: stage2: add initial impl of control flow in LLVM backend The following TZIR instrutions have been implemented in the backend: - all cmp operators (lt, lte, gt, gte, eq, neq) - block - br - condbr The following LLVMIR is generated for a simple assert function: ``` define void @assert(i1 %0) { Entry: %1 = alloca i1, align 1 store i1 %0, i1* %1, align 1 %2 = load i1, i1* %1, align 1 %3 = xor i1 %2, true br i1 %3, label %Then, label %Else Then: ; preds = %Entry call void @llvm.debugtrap() unreachable Else: ; preds = %Entry br label %Block Block: ; preds = %Else ret void } ``` See tests for more examples. --- src/codegen/llvm.zig | 177 +++++++++++++++++++++++++++++++++++------- src/codegen/llvm/bindings.zig | 34 ++++++++ test/stage2/llvm.zig | 71 +++++++++++++++++ 3 files changed, 256 insertions(+), 26 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index df97f7e0ef..8ec869a45e 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5,6 +5,7 @@ const Compilation = @import("../Compilation.zig"); const llvm = @import("llvm/bindings.zig"); const link = @import("../link.zig"); const log = std.log.scoped(.codegen); +const math = std.math; const Module = @import("../Module.zig"); const TypedValue = @import("../TypedValue.zig"); @@ -154,6 +155,8 @@ pub const LLVMIRModule = struct { /// This stores the LLVM values used in a function, such that they can be /// referred to in other instructions. This table is cleared before every function is generated. + /// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks + /// in here, however if a block ends, the instructions can be thrown away. func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value) = .{}, /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction. @@ -165,6 +168,18 @@ pub const LLVMIRModule = struct { /// to the top of the function. latest_alloca_inst: ?*const llvm.Value = null, + llvm_func: *const llvm.Value = undefined, + + /// This data structure is used to implement breaking to blocks. + blocks: std.AutoHashMapUnmanaged(*Inst.Block, struct { + parent_bb: *const llvm.BasicBlock, + break_bbs: *BreakBasicBlocks, + break_vals: *BreakValues, + }) = .{}, + + const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock); + const BreakValues = std.ArrayListUnmanaged(*const llvm.Value); + pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*LLVMIRModule { const self = try allocator.create(LLVMIRModule); errdefer allocator.destroy(self); @@ -252,6 +267,8 @@ pub const LLVMIRModule = struct { self.func_inst_table.deinit(self.gpa); self.gpa.free(self.object_path); + self.blocks.deinit(self.gpa); + allocator.destroy(self); } @@ -349,32 +366,9 @@ pub const LLVMIRModule = struct { self.entry_block = self.context.appendBasicBlock(llvm_func, "Entry"); self.builder.positionBuilderAtEnd(self.entry_block); self.latest_alloca_inst = null; + self.llvm_func = llvm_func; - const instructions = func.body.instructions; - for (instructions) |inst| { - const opt_llvm_val: ?*const llvm.Value = switch (inst.tag) { - .add => try self.genAdd(inst.castTag(.add).?), - .alloc => try self.genAlloc(inst.castTag(.alloc).?), - .arg => try self.genArg(inst.castTag(.arg).?), - .bitcast => try self.genBitCast(inst.castTag(.bitcast).?), - .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?), - .call => try self.genCall(inst.castTag(.call).?), - .intcast => try self.genIntCast(inst.castTag(.intcast).?), - .load => try self.genLoad(inst.castTag(.load).?), - .not => try self.genNot(inst.castTag(.not).?), - .ret => try self.genRet(inst.castTag(.ret).?), - .retvoid => self.genRetVoid(inst.castTag(.retvoid).?), - .store => try self.genStore(inst.castTag(.store).?), - .sub => try self.genSub(inst.castTag(.sub).?), - .unreach => self.genUnreach(inst.castTag(.unreach).?), - .dbg_stmt => blk: { - // TODO: implement debug info - break :blk null; - }, - else => |tag| return self.fail(src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}), - }; - if (opt_llvm_val) |llvm_val| try self.func_inst_table.putNoClobber(self.gpa, inst, llvm_val); - } + try self.genBody(func.body); } else if (typed_value.val.castTag(.extern_fn)) |extern_fn| { _ = try self.resolveLLVMFunction(extern_fn.data, src); } else { @@ -382,6 +376,42 @@ pub const LLVMIRModule = struct { } } + fn genBody(self: *LLVMIRModule, body: ir.Body) error{ OutOfMemory, CodegenFail }!void { + for (body.instructions) |inst| { + const opt_value = switch (inst.tag) { + .add => try self.genAdd(inst.castTag(.add).?), + .alloc => try self.genAlloc(inst.castTag(.alloc).?), + .arg => try self.genArg(inst.castTag(.arg).?), + .bitcast => try self.genBitCast(inst.castTag(.bitcast).?), + .block => try self.genBlock(inst.castTag(.block).?), + .br => try self.genBr(inst.castTag(.br).?), + .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?), + .call => try self.genCall(inst.castTag(.call).?), + .cmp_eq => try self.genCmp(inst.castTag(.cmp_eq).?, .eq), + .cmp_gt => try self.genCmp(inst.castTag(.cmp_gt).?, .gt), + .cmp_gte => try self.genCmp(inst.castTag(.cmp_gte).?, .gte), + .cmp_lt => try self.genCmp(inst.castTag(.cmp_lt).?, .lt), + .cmp_lte => try self.genCmp(inst.castTag(.cmp_lte).?, .lte), + .cmp_neq => try self.genCmp(inst.castTag(.cmp_neq).?, .neq), + .condbr => try self.genCondBr(inst.castTag(.condbr).?), + .intcast => try self.genIntCast(inst.castTag(.intcast).?), + .load => try self.genLoad(inst.castTag(.load).?), + .not => try self.genNot(inst.castTag(.not).?), + .ret => try self.genRet(inst.castTag(.ret).?), + .retvoid => self.genRetVoid(inst.castTag(.retvoid).?), + .store => try self.genStore(inst.castTag(.store).?), + .sub => try self.genSub(inst.castTag(.sub).?), + .unreach => self.genUnreach(inst.castTag(.unreach).?), + .dbg_stmt => blk: { + // TODO: implement debug info + break :blk null; + }, + else => |tag| return self.fail(inst.src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}), + }; + if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val); + } + } + fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !?*const llvm.Value { if (inst.func.value()) |func_value| { const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn| @@ -436,6 +466,99 @@ pub const LLVMIRModule = struct { return null; } + fn genCmp(self: *LLVMIRModule, inst: *Inst.BinOp, op: math.CompareOperator) !?*const llvm.Value { + const lhs = try self.resolveInst(inst.lhs); + const rhs = try self.resolveInst(inst.rhs); + + if (!inst.base.ty.isInt()) + if (inst.base.ty.tag() != .bool) + return self.fail(inst.base.src, "TODO implement 'genCmp' for type {}", .{inst.base.ty}); + + const is_signed = inst.base.ty.isSignedInt(); + const operation = switch (op) { + .eq => .EQ, + .neq => .NE, + .lt => @as(llvm.IntPredicate, if (is_signed) .SLT else .ULT), + .lte => @as(llvm.IntPredicate, if (is_signed) .SLE else .ULE), + .gt => @as(llvm.IntPredicate, if (is_signed) .SGT else .UGT), + .gte => @as(llvm.IntPredicate, if (is_signed) .SGE else .UGE), + }; + + return self.builder.buildICmp(operation, lhs, rhs, ""); + } + + fn genBlock(self: *LLVMIRModule, inst: *Inst.Block) !?*const llvm.Value { + const parent_bb = self.context.createBasicBlock("Block"); + + // 5 breaks to a block seems like a reasonable default. + var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa, 5); + var break_vals = try BreakValues.initCapacity(self.gpa, 5); + try self.blocks.putNoClobber(self.gpa, inst, .{ + .parent_bb = parent_bb, + .break_bbs = &break_bbs, + .break_vals = &break_vals, + }); + defer { + self.blocks.removeAssertDiscard(inst); + break_bbs.deinit(self.gpa); + break_vals.deinit(self.gpa); + } + + try self.genBody(inst.body); + + self.llvm_func.appendExistingBasicBlock(parent_bb); + self.builder.positionBuilderAtEnd(parent_bb); + + // If the block does not return a value, we dont have to create a phi node. + if (!inst.base.ty.hasCodeGenBits()) return null; + + const phi_node = self.builder.buildPhi(try self.getLLVMType(inst.base.ty, inst.base.src), ""); + phi_node.addIncoming( + break_vals.items.ptr, + break_bbs.items.ptr, + @intCast(c_uint, break_vals.items.len), + ); + return phi_node; + } + + fn genBr(self: *LLVMIRModule, inst: *Inst.Br) !?*const llvm.Value { + // Get the block that we want to break to. + var block = self.blocks.get(inst.block).?; + _ = self.builder.buildBr(block.parent_bb); + + // If the break doesn't break a value, then we don't have to add + // the values to the lists. + if (!inst.operand.ty.hasCodeGenBits()) return null; + + // For the phi node, we need the basic blocks and the values of the + // break instructions. + try block.break_bbs.append(self.gpa, self.builder.getInsertBlock()); + + const val = try self.resolveInst(inst.operand); + try block.break_vals.append(self.gpa, val); + + return null; + } + + fn genCondBr(self: *LLVMIRModule, inst: *Inst.CondBr) !?*const llvm.Value { + const condition_value = try self.resolveInst(inst.condition); + + const then_block = self.context.appendBasicBlock(self.llvm_func, "Then"); + const else_block = self.context.appendBasicBlock(self.llvm_func, "Else"); + { + const prev_block = self.builder.getInsertBlock(); + defer self.builder.positionBuilderAtEnd(prev_block); + + self.builder.positionBuilderAtEnd(then_block); + try self.genBody(inst.then_body); + + self.builder.positionBuilderAtEnd(else_block); + try self.genBody(inst.else_body); + } + _ = self.builder.buildCondBr(condition_value, then_block, else_block); + return null; + } + fn genNot(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { return self.builder.buildNot(try self.resolveInst(inst.operand), ""); } @@ -509,6 +632,9 @@ pub const LLVMIRModule = struct { /// Use this instead of builder.buildAlloca, because this function makes sure to /// put the alloca instruction at the top of the function! fn buildAlloca(self: *LLVMIRModule, t: *const llvm.Type) *const llvm.Value { + const prev_block = self.builder.getInsertBlock(); + defer self.builder.positionBuilderAtEnd(prev_block); + if (self.latest_alloca_inst) |latest_alloc| { // builder.positionBuilder adds it before the instruction, // but we want to put it after the last alloca instruction. @@ -521,7 +647,6 @@ pub const LLVMIRModule = struct { self.builder.positionBuilder(self.entry_block, first_inst); } } - defer self.builder.positionBuilderAtEnd(self.entry_block); const val = self.builder.buildAlloca(t, ""); self.latest_alloca_inst = val; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index cd928877b1..8774260a08 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -24,6 +24,9 @@ pub const Context = opaque { pub const constString = LLVMConstStringInContext; extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: LLVMBool) *const Value; + pub const createBasicBlock = LLVMCreateBasicBlockInContext; + extern fn LLVMCreateBasicBlockInContext(C: *const Context, Name: [*:0]const u8) *const BasicBlock; + pub const appendBasicBlock = LLVMAppendBasicBlockInContext; extern fn LLVMAppendBasicBlockInContext(C: *const Context, Fn: *const Value, Name: [*:0]const u8) *const BasicBlock; @@ -38,6 +41,12 @@ pub const Value = opaque { pub const getFirstBasicBlock = LLVMGetFirstBasicBlock; extern fn LLVMGetFirstBasicBlock(Fn: *const Value) ?*const BasicBlock; + pub const appendExistingBasicBlock = LLVMAppendExistingBasicBlock; + extern fn LLVMAppendExistingBasicBlock(Fn: *const Value, BB: *const BasicBlock) void; + + pub const addIncoming = LLVMAddIncoming; + extern fn LLVMAddIncoming(PhiNode: *const Value, IncomingValues: [*]*const Value, IncomingBlocks: [*]*const BasicBlock, Count: c_uint) void; + pub const getNextInstruction = LLVMGetNextInstruction; extern fn LLVMGetNextInstruction(Inst: *const Value) ?*const Value; }; @@ -183,6 +192,31 @@ pub const Builder = opaque { pub const buildInBoundsGEP = LLVMBuildInBoundsGEP; extern fn LLVMBuildInBoundsGEP(B: *const Builder, Pointer: *const Value, Indices: [*]*const Value, NumIndices: c_uint, Name: [*:0]const u8) *const Value; + + pub const buildICmp = LLVMBuildICmp; + extern fn LLVMBuildICmp(*const Builder, Op: IntPredicate, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildBr = LLVMBuildBr; + extern fn LLVMBuildBr(*const Builder, Dest: *const BasicBlock) *const Value; + + pub const buildCondBr = LLVMBuildCondBr; + extern fn LLVMBuildCondBr(*const Builder, If: *const Value, Then: *const BasicBlock, Else: *const BasicBlock) *const Value; + + pub const buildPhi = LLVMBuildPhi; + extern fn LLVMBuildPhi(*const Builder, Ty: *const Type, Name: [*:0]const u8) *const Value; +}; + +pub const IntPredicate = extern enum { + EQ = 32, + NE = 33, + UGT = 34, + UGE = 35, + ULT = 36, + ULE = 37, + SGT = 38, + SGE = 39, + SLT = 40, + SLE = 41, }; pub const BasicBlock = opaque { diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig index ebb5fc390f..eecea3d1f0 100644 --- a/test/stage2/llvm.zig +++ b/test/stage2/llvm.zig @@ -40,4 +40,75 @@ pub fn addCases(ctx: *TestContext) !void { \\} , "hello world!" ++ std.cstr.line_sep); } + + { + var case = ctx.exeUsingLlvmBackend("simple if statement", linux_x64); + + case.addCompareOutput( + \\fn add(a: i32, b: i32) i32 { + \\ return a + b; + \\} + \\ + \\fn assert(ok: bool) void { + \\ if (!ok) unreachable; + \\} + \\ + \\export fn main() c_int { + \\ assert(add(1,2) == 3); + \\ return 0; + \\} + , ""); + } + + { + var case = ctx.exeUsingLlvmBackend("blocks", linux_x64); + + case.addCompareOutput( + \\fn assert(ok: bool) void { + \\ if (!ok) unreachable; + \\} + \\ + \\fn foo(ok: bool) i32 { + \\ const val: i32 = blk: { + \\ var x: i32 = 1; + \\ if (!ok) break :blk x + 9; + \\ break :blk x + 19; + \\ }; + \\ return val + 10; + \\} + \\ + \\export fn main() c_int { + \\ assert(foo(false) == 20); + \\ assert(foo(true) == 30); + \\ return 0; + \\} + , ""); + } + + { + var case = ctx.exeUsingLlvmBackend("nested blocks", linux_x64); + + case.addCompareOutput( + \\fn assert(ok: bool) void { + \\ if (!ok) unreachable; + \\} + \\ + \\fn foo(ok: bool) i32 { + \\ var val: i32 = blk: { + \\ const val2: i32 = another: { + \\ if (!ok) break :blk 10; + \\ break :another 10; + \\ }; + \\ break :blk val2 + 10; + \\ }; + \\ return val; + \\} + \\ + \\export fn main() c_int { + \\ assert(foo(false) == 10); + \\ assert(foo(true) == 20); + \\ return 0; + \\} + , ""); + } } -- cgit v1.2.3 From e1d8073d2fc0df6fbc5ce983312e3da374a9889b Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Sun, 10 Jan 2021 17:36:01 +0100 Subject: stage2: add support for loops in LLVM backend A simple `while(true) {}` loop generates the following LLVMIR: ``` define i32 @main() { Entry: br label %Loop Loop: ; preds = %Loop, %Entry br label %Loop } ``` Also implement TZIR printing for loops and add a corresponding test. --- src/codegen/llvm.zig | 12 ++++++++++++ src/zir.zig | 21 +++++++++++++++++++-- test/stage2/llvm.zig | 21 +++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d6515f9ec6..5d753c41cb 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -396,6 +396,7 @@ pub const LLVMIRModule = struct { .condbr => try self.genCondBr(inst.castTag(.condbr).?), .intcast => try self.genIntCast(inst.castTag(.intcast).?), .load => try self.genLoad(inst.castTag(.load).?), + .loop => try self.genLoop(inst.castTag(.loop).?), .not => try self.genNot(inst.castTag(.not).?), .ret => try self.genRet(inst.castTag(.ret).?), .retvoid => self.genRetVoid(inst.castTag(.retvoid).?), @@ -559,6 +560,17 @@ pub const LLVMIRModule = struct { return null; } + fn genLoop(self: *LLVMIRModule, inst: *Inst.Loop) !?*const llvm.Value { + const loop_block = self.context.appendBasicBlock(self.llvm_func, "Loop"); + _ = self.builder.buildBr(loop_block); + + self.builder.positionBuilderAtEnd(loop_block); + try self.genBody(inst.body); + + _ = self.builder.buildBr(loop_block); + return null; + } + fn genNot(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value { return self.builder.buildNot(try self.resolveInst(inst.operand), ""); } diff --git a/src/zir.zig b/src/zir.zig index 4aee20991c..8d99218b3d 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -2011,11 +2011,15 @@ const DumpTzir = struct { try dtz.fetchInstsAndResolveConsts(condbr.else_body); }, + .loop => { + const loop = inst.castTag(.loop).?; + try dtz.fetchInstsAndResolveConsts(loop.body); + }, + // TODO fill out this debug printing .assembly, .call, .constant, - .loop, .varptr, .switchbr, => {}, @@ -2229,11 +2233,24 @@ const DumpTzir = struct { try writer.writeAll(")\n"); }, + .loop => { + const loop = inst.castTag(.loop).?; + + try writer.writeAll("\n"); + + const old_indent = dtz.indent; + dtz.indent += 2; + try dtz.dumpBody(loop.body, writer); + dtz.indent = old_indent; + + try writer.writeByteNTimes(' ', dtz.indent); + try writer.writeAll(")\n"); + }, + // TODO fill out this debug printing .assembly, .call, .constant, - .loop, .varptr, .switchbr, => { diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig index eecea3d1f0..69622714a7 100644 --- a/test/stage2/llvm.zig +++ b/test/stage2/llvm.zig @@ -111,4 +111,25 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + + { + var case = ctx.exeUsingLlvmBackend("while loops", linux_x64); + + case.addCompareOutput( + \\fn assert(ok: bool) void { + \\ if (!ok) unreachable; + \\} + \\ + \\export fn main() c_int { + \\ var sum: u32 = 0; + \\ var i: u32 = 0; + \\ while (i < 5) : (i += 1) { + \\ sum += i; + \\ } + \\ assert(sum == 10); + \\ assert(i == 5); + \\ return 0; + \\} + , ""); + } } -- cgit v1.2.3 From bb74f72e97f3d503c4801bb7ca3d6412ad01b28c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 10 Jan 2021 18:51:01 +0100 Subject: stage2: refactor wasm backend - similar to the other backends --- src/codegen/wasm.zig | 364 ++++++++++++++++++++++++++++++++++++--------------- src/link/Wasm.zig | 27 +++- 2 files changed, 283 insertions(+), 108 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 036243dcca..97703ee42a 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -7,136 +7,292 @@ const mem = std.mem; const Module = @import("../Module.zig"); const Decl = Module.Decl; -const Inst = @import("../ir.zig").Inst; +const ir = @import("../ir.zig"); +const Inst = ir.Inst; const Type = @import("../type.zig").Type; const Value = @import("../value.zig").Value; +const Compilation = @import("../Compilation.zig"); -fn genValtype(ty: Type) u8 { +/// Wasm Value, created when generating an instruction +const WValue = union(enum) { + none: void, + /// Index of the local variable + local: u32, + /// A constant instruction + constant: *Inst, + /// Each newly created wasm block have a label + /// in the form of an index. + block_idx: u32, +}; + +pub const ValueTable = std.AutoArrayHashMap(*Inst, WValue); + +/// Using a given Zig type, returns the corresponding wasm value type +fn genValtype(ty: Type) ?u8 { return switch (ty.tag()) { - .u32, .i32 => 0x7F, - .u64, .i64 => 0x7E, .f32 => 0x7D, .f64 => 0x7C, - else => @panic("TODO: Implement more types for wasm."), + .u32, .i32 => 0x7F, + .u64, .i64 => 0x7E, + else => null, }; } -pub fn genFunctype(buf: *ArrayList(u8), decl: *Decl) !void { - const ty = decl.typed_value.most_recent.typed_value.ty; - const writer = buf.writer(); +/// Code represents the `Code` section of wasm that +/// belongs to a function +pub const Code = struct { + /// Reference to the function declaration the code + /// section belongs to + decl: *Decl, + gpa: *mem.Allocator, + /// Table to save `WValue`'s generated by an `Inst` + values: ValueTable, + /// `bytes` contains the wasm instructions that have been emitted + /// this is what will be emitted after codegen to write the wasm binary + bytes: ArrayList(u8), + /// Contains the generated function type bytecode for the current function + func_type_data: ArrayList(u8), + /// The index the next local generated will have + /// NOTE: arguments share the index with locals therefore the first variable + /// will have the index that comes after the last argument's index + local_index: u32 = 0, + /// The index the next argument generated will have + arg_index: u32 = 0, + /// If codegen fails, an error messages will be allocated and saved + /// in `err_msg` + err_msg: *Compilation.ErrorMsg, + + const InnerError = error{ + OutOfMemory, + CodegenFail, + }; + + fn fail(self: *Code, src: usize, comptime fmt: []const u8, args: anytype) InnerError { + self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, fmt, args); + return error.CodegenFail; + } + + /// Returns the `WValue` for the given `inst` + /// creates a new WValue for constants and returns that instead + fn resolveInst(self: Code, inst: *Inst) !WValue { + if (inst.value()) |_| { + return WValue{ .constant = inst }; + } + + return self.values.get(inst).?; // Instruction does not dominate all uses! + } + + /// Writes the bytecode depending on the given `WValue` in `val` + fn emitWValue(self: *Code, val: WValue) !void { + const writer = self.bytes.writer(); + switch (val) { + .none => unreachable, + .block_idx => unreachable, + // loads the local onto the stack at the given index + .local => |idx| { + // local.set + try writer.writeByte(0x20); + try leb.writeULEB128(writer, idx); + }, + // creates a new constant onto the stack + .constant => |inst| try self.emitConstant(inst.castTag(.constant).?), + } + } - // functype magic - try writer.writeByte(0x60); + fn genFunctype(self: *Code) !void { + const ty = self.decl.typed_value.most_recent.typed_value.ty; + const writer = self.func_type_data.writer(); - // param types - try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); - if (ty.fnParamLen() != 0) { - const params = try buf.allocator.alloc(Type, ty.fnParamLen()); - defer buf.allocator.free(params); - ty.fnParamTypes(params); - for (params) |param_type| try writer.writeByte(genValtype(param_type)); + // functype magic + try writer.writeByte(0x60); + + // param types + try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); + if (ty.fnParamLen() != 0) { + const params = try self.gpa.alloc(Type, ty.fnParamLen()); + defer self.gpa.free(params); + ty.fnParamTypes(params); + for (params) |param_type| { + const val_type = genValtype(param_type) orelse + return self.fail(self.decl.src(), "TODO: Wasm generate wasm type value for type '{s}'", .{param_type.tag()}); + try writer.writeByte(val_type); + } + } + + // return type + const return_type = ty.fnReturnType(); + switch (return_type.tag()) { + .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)), + else => |ret_type| { + try leb.writeULEB128(writer, @as(u32, 1)); + const val_type = genValtype(return_type) orelse + return self.fail(self.decl.src(), "TODO: Wasm generate wasm return type value for type '{s}'", .{ret_type}); + try writer.writeByte(val_type); + }, + } } - // return type - const return_type = ty.fnReturnType(); - switch (return_type.tag()) { - .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)), - else => { + /// Generates the wasm bytecode for the given `code` + pub fn gen(self: *Code) !void { + assert(self.bytes.items.len == 0); + try self.genFunctype(); + const writer = self.bytes.writer(); + + // Reserve space to write the size after generating the code + try self.bytes.resize(5); + + // Write instructions + // TODO: check for and handle death of instructions + const tv = self.decl.typed_value.most_recent.typed_value; + const mod_fn = tv.val.castTag(.function).?.data; + + var locals = std.ArrayList(u8).init(self.gpa); + defer locals.deinit(); + + for (mod_fn.body.instructions) |inst| { + if (inst.tag != .alloc) continue; + + const alloc: *Inst.NoOp = inst.castTag(.alloc).?; + const elem_type = alloc.base.ty.elemType(); + + const wasm_type = genValtype(elem_type) orelse + return self.fail(inst.src, "TODO: Wasm generate wasm type value for type '{s}'", .{elem_type.tag()}); + + try locals.append(wasm_type); + } + + try leb.writeULEB128(writer, @intCast(u32, locals.items.len)); + + // emit the actual locals amount + for (locals.items) |local| { try leb.writeULEB128(writer, @as(u32, 1)); - try writer.writeByte(genValtype(return_type)); - }, + try leb.writeULEB128(writer, local); // valtype + } + + for (mod_fn.body.instructions) |inst| { + const result = try self.genInst(inst); + + if (result != .none) { + try self.values.putNoClobber(inst, result); + } + } + + // Write 'end' opcode + try writer.writeByte(0x0B); + + // Fill in the size of the generated code to the reserved space at the + // beginning of the buffer. + const size = self.bytes.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5; + leb.writeUnsignedFixed(5, self.bytes.items[0..5], @intCast(u32, size)); } -} -pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void { - assert(buf.items.len == 0); - const writer = buf.writer(); + fn genInst(self: *Code, inst: *Inst) !WValue { + return switch (inst.tag) { + .alloc => self.genAlloc(inst.castTag(.alloc).?), + .arg => self.genArg(inst.castTag(.arg).?), + .call => self.genCall(inst.castTag(.call).?), + .constant => unreachable, + .dbg_stmt => WValue.none, + .load => self.genLoad(inst.castTag(.load).?), + .ret => self.genRet(inst.castTag(.ret).?), + .retvoid => WValue.none, + .store => self.genStore(inst.castTag(.store).?), + else => self.fail(inst.src, "TODO: Implement wasm inst: {s}", .{inst.tag}), + }; + } - // Reserve space to write the size after generating the code - try buf.resize(5); + fn genRet(self: *Code, inst: *Inst.UnOp) !WValue { + const operand = try self.resolveInst(inst.operand); + try self.emitWValue(operand); + return WValue.none; + } - // Write the size of the locals vec - // TODO: implement locals - try leb.writeULEB128(writer, @as(u32, 0)); + fn genCall(self: *Code, inst: *Inst.Call) !WValue { + const func_inst = inst.func.castTag(.constant).?; + const func = func_inst.val.castTag(.function).?.data; + const target = func.owner_decl; + const target_ty = target.typed_value.most_recent.typed_value.ty; - // Write instructions - // TODO: check for and handle death of instructions - const tv = decl.typed_value.most_recent.typed_value; - const mod_fn = tv.val.castTag(.function).?.data; - for (mod_fn.body.instructions) |inst| try genInst(buf, decl, inst); + for (inst.args) |arg| { + const arg_val = try self.resolveInst(arg); + try self.emitWValue(arg_val); + } - // Write 'end' opcode - try writer.writeByte(0x0B); + try self.bytes.append(0x10); // call - // Fill in the size of the generated code to the reserved space at the - // beginning of the buffer. - const size = buf.items.len - 5 + decl.fn_link.wasm.?.idx_refs.items.len * 5; - leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, size)); -} + // The function index immediate argument will be filled in using this data + // in link.Wasm.flush(). + try self.decl.fn_link.wasm.?.idx_refs.append(self.gpa, .{ + .offset = @intCast(u32, self.bytes.items.len), + .decl = target, + }); -fn genInst(buf: *ArrayList(u8), decl: *Decl, inst: *Inst) !void { - return switch (inst.tag) { - .call => genCall(buf, decl, inst.castTag(.call).?), - .constant => genConstant(buf, decl, inst.castTag(.constant).?), - .dbg_stmt => {}, - .ret => genRet(buf, decl, inst.castTag(.ret).?), - .retvoid => {}, - else => error.TODOImplementMoreWasmCodegen, - }; -} + return WValue.none; + } -fn genConstant(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Constant) !void { - const writer = buf.writer(); - switch (inst.base.ty.tag()) { - .u32 => { - try writer.writeByte(0x41); // i32.const - try leb.writeILEB128(writer, inst.val.toUnsignedInt()); - }, - .i32 => { - try writer.writeByte(0x41); // i32.const - try leb.writeILEB128(writer, inst.val.toSignedInt()); - }, - .u64 => { - try writer.writeByte(0x42); // i64.const - try leb.writeILEB128(writer, inst.val.toUnsignedInt()); - }, - .i64 => { - try writer.writeByte(0x42); // i64.const - try leb.writeILEB128(writer, inst.val.toSignedInt()); - }, - .f32 => { - try writer.writeByte(0x43); // f32.const - // TODO: enforce LE byte order - try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32))); - }, - .f64 => { - try writer.writeByte(0x44); // f64.const - // TODO: enforce LE byte order - try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64))); - }, - .void => {}, - else => return error.TODOImplementMoreWasmCodegen, + fn genAlloc(self: *Code, inst: *Inst.NoOp) !WValue { + defer self.local_index += 1; + return WValue{ .local = self.local_index }; } -} -fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void { - try genInst(buf, decl, inst.operand); -} + fn genStore(self: *Code, inst: *Inst.BinOp) !WValue { + const writer = self.bytes.writer(); -fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void { - const func_inst = inst.func.castTag(.constant).?; - const func = func_inst.val.castTag(.function).?.data; - const target = func.owner_decl; - const target_ty = target.typed_value.most_recent.typed_value.ty; + const lhs = try self.resolveInst(inst.lhs); - if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen; + const rhs = try self.resolveInst(inst.rhs); + try self.emitWValue(rhs); - try buf.append(0x10); // call + try writer.writeByte(0x21); // local.set + try leb.writeULEB128(writer, lhs.local); - // The function index immediate argument will be filled in using this data - // in link.Wasm.flush(). - try decl.fn_link.wasm.?.idx_refs.append(buf.allocator, .{ - .offset = @intCast(u32, buf.items.len), - .decl = target, - }); -} + return WValue.none; + } + + fn genLoad(self: *Code, inst: *Inst.UnOp) !WValue { + const operand = self.resolveInst(inst.operand); + + // ensure index to local + return WValue{ .local = operand.local }; + } + + fn genArg(self: *Code, inst: *Inst.Arg) !WValue { + // arguments share the index with locals + defer self.local_index += 1; + return WValue{ .local = self.local_index }; + } + + fn emitConstant(self: *Code, inst: *Inst.Constant) !void { + const writer = self.bytes.writer(); + switch (inst.base.ty.tag()) { + .u32 => { + try writer.writeByte(0x41); // i32.const + try leb.writeILEB128(writer, inst.val.toUnsignedInt()); + }, + .i32 => { + try writer.writeByte(0x41); // i32.const + try leb.writeILEB128(writer, inst.val.toSignedInt()); + }, + .u64 => { + try writer.writeByte(0x42); // i64.const + try leb.writeILEB128(writer, inst.val.toUnsignedInt()); + }, + .i64 => { + try writer.writeByte(0x42); // i64.const + try leb.writeILEB128(writer, inst.val.toSignedInt()); + }, + .f32 => { + try writer.writeByte(0x43); // f32.const + // TODO: enforce LE byte order + try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32))); + }, + .f64 => { + try writer.writeByte(0x44); // f64.const + // TODO: enforce LE byte order + try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64))); + }, + .void => {}, + else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}), + } + } +}; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 576be2d306..c5e86ada3d 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -118,10 +118,29 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { var managed_functype = fn_data.functype.toManaged(self.base.allocator); var managed_code = fn_data.code.toManaged(self.base.allocator); - try codegen.genFunctype(&managed_functype, decl); - try codegen.genCode(&managed_code, decl); - fn_data.functype = managed_functype.toUnmanaged(); - fn_data.code = managed_code.toUnmanaged(); + + var code = codegen.Code{ + .gpa = self.base.allocator, + .values = codegen.ValueTable.init(self.base.allocator), + .bytes = managed_code, + .func_type_data = managed_functype, + .decl = decl, + .err_msg = undefined, + }; + defer code.values.deinit(); + + // generate the 'code' section for the function declaration + code.gen() catch |err| switch (err) { + error.CodegenFail => { + decl.analysis = .codegen_failure; + try module.failed_decls.put(module.gpa, decl, code.err_msg); + return; + }, + else => |e| return err, + }; + + fn_data.functype = code.func_type_data.toUnmanaged(); + fn_data.code = code.bytes.toUnmanaged(); } pub fn updateDeclExports( -- cgit v1.2.3 From 4b2538f72c9190b12c11c45a6d18cdc831b41015 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Fri, 15 Jan 2021 23:24:47 +0100 Subject: Cleanup and 'add' instruction for bigger test area --- src/codegen/wasm.zig | 110 ++++++++++++++++++++++++++------------------------- src/link/Wasm.zig | 12 +++--- 2 files changed, 63 insertions(+), 59 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 97703ee42a..a67e3000e9 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -18,16 +18,16 @@ const WValue = union(enum) { none: void, /// Index of the local variable local: u32, - /// A constant instruction + /// Instruction holding a constant `Value` constant: *Inst, - /// Each newly created wasm block have a label - /// in the form of an index. + /// Block label block_idx: u32, }; -pub const ValueTable = std.AutoArrayHashMap(*Inst, WValue); +/// Hashmap to store generated `WValue` for each `Inst` +pub const ValueTable = std.AutoHashMap(*Inst, WValue); -/// Using a given Zig type, returns the corresponding wasm value type +/// Using a given `Type`, returns the corresponding wasm value type fn genValtype(ty: Type) ?u8 { return switch (ty.tag()) { .f32 => 0x7D, @@ -40,24 +40,22 @@ fn genValtype(ty: Type) ?u8 { /// Code represents the `Code` section of wasm that /// belongs to a function -pub const Code = struct { +pub const Context = struct { /// Reference to the function declaration the code /// section belongs to decl: *Decl, gpa: *mem.Allocator, /// Table to save `WValue`'s generated by an `Inst` values: ValueTable, - /// `bytes` contains the wasm instructions that have been emitted - /// this is what will be emitted after codegen to write the wasm binary + /// `bytes` contains the wasm bytecode belonging to the 'code' section. bytes: ArrayList(u8), /// Contains the generated function type bytecode for the current function + /// found in `decl` func_type_data: ArrayList(u8), /// The index the next local generated will have /// NOTE: arguments share the index with locals therefore the first variable /// will have the index that comes after the last argument's index local_index: u32 = 0, - /// The index the next argument generated will have - arg_index: u32 = 0, /// If codegen fails, an error messages will be allocated and saved /// in `err_msg` err_msg: *Compilation.ErrorMsg, @@ -67,14 +65,15 @@ pub const Code = struct { CodegenFail, }; - fn fail(self: *Code, src: usize, comptime fmt: []const u8, args: anytype) InnerError { + /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig + fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError { self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, fmt, args); return error.CodegenFail; } - /// Returns the `WValue` for the given `inst` - /// creates a new WValue for constants and returns that instead - fn resolveInst(self: Code, inst: *Inst) !WValue { + /// Resolves the `WValue` for the given instruction `inst` + /// When the given instruction has a `Value`, it returns a constant instead + fn resolveInst(self: Context, inst: *Inst) WValue { if (inst.value()) |_| { return WValue{ .constant = inst }; } @@ -83,23 +82,19 @@ pub const Code = struct { } /// Writes the bytecode depending on the given `WValue` in `val` - fn emitWValue(self: *Code, val: WValue) !void { + fn emitWValue(self: *Context, val: WValue) InnerError!void { const writer = self.bytes.writer(); switch (val) { - .none => unreachable, - .block_idx => unreachable, - // loads the local onto the stack at the given index + .none, .block_idx => {}, .local => |idx| { - // local.set - try writer.writeByte(0x20); + try writer.writeByte(0x20); // local.get try leb.writeULEB128(writer, idx); }, - // creates a new constant onto the stack - .constant => |inst| try self.emitConstant(inst.castTag(.constant).?), + .constant => |inst| try self.emitConstant(inst.castTag(.constant).?), // creates a new constant onto the stack } } - fn genFunctype(self: *Code) !void { + fn genFunctype(self: *Context) InnerError!void { const ty = self.decl.typed_value.most_recent.typed_value.ty; const writer = self.func_type_data.writer(); @@ -114,7 +109,7 @@ pub const Code = struct { ty.fnParamTypes(params); for (params) |param_type| { const val_type = genValtype(param_type) orelse - return self.fail(self.decl.src(), "TODO: Wasm generate wasm type value for type '{s}'", .{param_type.tag()}); + return self.fail(self.decl.src(), "TODO: Wasm codegen - arg type value for type '{s}'", .{param_type.tag()}); try writer.writeByte(val_type); } } @@ -126,14 +121,14 @@ pub const Code = struct { else => |ret_type| { try leb.writeULEB128(writer, @as(u32, 1)); const val_type = genValtype(return_type) orelse - return self.fail(self.decl.src(), "TODO: Wasm generate wasm return type value for type '{s}'", .{ret_type}); + return self.fail(self.decl.src(), "TODO: Wasm codegen - return type value for type '{s}'", .{ret_type}); try writer.writeByte(val_type); }, } } /// Generates the wasm bytecode for the given `code` - pub fn gen(self: *Code) !void { + pub fn gen(self: *Context) InnerError!void { assert(self.bytes.items.len == 0); try self.genFunctype(); const writer = self.bytes.writer(); @@ -156,7 +151,7 @@ pub const Code = struct { const elem_type = alloc.base.ty.elemType(); const wasm_type = genValtype(elem_type) orelse - return self.fail(inst.src, "TODO: Wasm generate wasm type value for type '{s}'", .{elem_type.tag()}); + return self.fail(inst.src, "TODO: Wasm codegen - valtype for type '{s}'", .{elem_type.tag()}); try locals.append(wasm_type); } @@ -169,16 +164,9 @@ pub const Code = struct { try leb.writeULEB128(writer, local); // valtype } - for (mod_fn.body.instructions) |inst| { - const result = try self.genInst(inst); - - if (result != .none) { - try self.values.putNoClobber(inst, result); - } - } + try self.genBody(mod_fn.body); - // Write 'end' opcode - try writer.writeByte(0x0B); + try writer.writeByte(0x0B); // end // Fill in the size of the generated code to the reserved space at the // beginning of the buffer. @@ -186,8 +174,9 @@ pub const Code = struct { leb.writeUnsignedFixed(5, self.bytes.items[0..5], @intCast(u32, size)); } - fn genInst(self: *Code, inst: *Inst) !WValue { + fn genInst(self: *Context, inst: *Inst) InnerError!WValue { return switch (inst.tag) { + .add => self.genAdd(inst.castTag(.add).?), .alloc => self.genAlloc(inst.castTag(.alloc).?), .arg => self.genArg(inst.castTag(.arg).?), .call => self.genCall(inst.castTag(.call).?), @@ -201,20 +190,27 @@ pub const Code = struct { }; } - fn genRet(self: *Code, inst: *Inst.UnOp) !WValue { - const operand = try self.resolveInst(inst.operand); + fn genBody(self: *Context, body: ir.Body) InnerError!void { + for (body.instructions) |inst| { + const result = try self.genInst(inst); + try self.values.putNoClobber(inst, result); + } + } + + fn genRet(self: *Context, inst: *Inst.UnOp) InnerError!WValue { + const operand = self.resolveInst(inst.operand); try self.emitWValue(operand); return WValue.none; } - fn genCall(self: *Code, inst: *Inst.Call) !WValue { + fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue { const func_inst = inst.func.castTag(.constant).?; const func = func_inst.val.castTag(.function).?.data; const target = func.owner_decl; const target_ty = target.typed_value.most_recent.typed_value.ty; for (inst.args) |arg| { - const arg_val = try self.resolveInst(arg); + const arg_val = self.resolveInst(arg); try self.emitWValue(arg_val); } @@ -230,39 +226,47 @@ pub const Code = struct { return WValue.none; } - fn genAlloc(self: *Code, inst: *Inst.NoOp) !WValue { + fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { defer self.local_index += 1; return WValue{ .local = self.local_index }; } - fn genStore(self: *Code, inst: *Inst.BinOp) !WValue { + fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue { const writer = self.bytes.writer(); - const lhs = try self.resolveInst(inst.lhs); - - const rhs = try self.resolveInst(inst.rhs); + const lhs = self.resolveInst(inst.lhs); + const rhs = self.resolveInst(inst.rhs); try self.emitWValue(rhs); try writer.writeByte(0x21); // local.set try leb.writeULEB128(writer, lhs.local); - return WValue.none; } - fn genLoad(self: *Code, inst: *Inst.UnOp) !WValue { + fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue { const operand = self.resolveInst(inst.operand); - - // ensure index to local - return WValue{ .local = operand.local }; + try self.emitWValue(operand); + return WValue.none; } - fn genArg(self: *Code, inst: *Inst.Arg) !WValue { + fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue { // arguments share the index with locals defer self.local_index += 1; return WValue{ .local = self.local_index }; } - fn emitConstant(self: *Code, inst: *Inst.Constant) !void { + fn genAdd(self: *Context, inst: *Inst.BinOp) InnerError!WValue { + const lhs = self.resolveInst(inst.lhs); + const rhs = self.resolveInst(inst.rhs); + + try self.emitWValue(lhs); + try self.emitWValue(rhs); + + try self.bytes.append(0x6A); // i32.add + return WValue.none; + } + + fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void { const writer = self.bytes.writer(); switch (inst.base.ty.tag()) { .u32 => { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index c5e86ada3d..8e3d79d4db 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -119,7 +119,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { var managed_functype = fn_data.functype.toManaged(self.base.allocator); var managed_code = fn_data.code.toManaged(self.base.allocator); - var code = codegen.Code{ + var context = codegen.Context{ .gpa = self.base.allocator, .values = codegen.ValueTable.init(self.base.allocator), .bytes = managed_code, @@ -127,20 +127,20 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { .decl = decl, .err_msg = undefined, }; - defer code.values.deinit(); + defer context.values.deinit(); // generate the 'code' section for the function declaration - code.gen() catch |err| switch (err) { + context.gen() catch |err| switch (err) { error.CodegenFail => { decl.analysis = .codegen_failure; - try module.failed_decls.put(module.gpa, decl, code.err_msg); + try module.failed_decls.put(module.gpa, decl, context.err_msg); return; }, else => |e| return err, }; - fn_data.functype = code.func_type_data.toUnmanaged(); - fn_data.code = code.bytes.toUnmanaged(); + fn_data.functype = context.func_type_data.toUnmanaged(); + fn_data.code = context.bytes.toUnmanaged(); } pub fn updateDeclExports( -- cgit v1.2.3 From 6c19aeddca7f1f2c25c7d34dd9f6011b495670a9 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sat, 16 Jan 2021 14:47:06 +0100 Subject: Add tests and move tests to wasm's own file --- src/codegen/wasm.zig | 32 ++++++------- src/link/Wasm.zig | 4 +- test/stage2/test.zig | 62 +------------------------ test/stage2/wasm.zig | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 78 deletions(-) create mode 100644 test/stage2/wasm.zig (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index a67e3000e9..b5e044b258 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -48,7 +48,7 @@ pub const Context = struct { /// Table to save `WValue`'s generated by an `Inst` values: ValueTable, /// `bytes` contains the wasm bytecode belonging to the 'code' section. - bytes: ArrayList(u8), + code: ArrayList(u8), /// Contains the generated function type bytecode for the current function /// found in `decl` func_type_data: ArrayList(u8), @@ -56,8 +56,7 @@ pub const Context = struct { /// NOTE: arguments share the index with locals therefore the first variable /// will have the index that comes after the last argument's index local_index: u32 = 0, - /// If codegen fails, an error messages will be allocated and saved - /// in `err_msg` + /// If codegen fails, an error messages will be allocated and saved in `err_msg` err_msg: *Compilation.ErrorMsg, const InnerError = error{ @@ -74,6 +73,8 @@ pub const Context = struct { /// Resolves the `WValue` for the given instruction `inst` /// When the given instruction has a `Value`, it returns a constant instead fn resolveInst(self: Context, inst: *Inst) WValue { + if (!inst.ty.hasCodeGenBits()) return .none; + if (inst.value()) |_| { return WValue{ .constant = inst }; } @@ -83,7 +84,7 @@ pub const Context = struct { /// Writes the bytecode depending on the given `WValue` in `val` fn emitWValue(self: *Context, val: WValue) InnerError!void { - const writer = self.bytes.writer(); + const writer = self.code.writer(); switch (val) { .none, .block_idx => {}, .local => |idx| { @@ -127,14 +128,14 @@ pub const Context = struct { } } - /// Generates the wasm bytecode for the given `code` + /// Generates the wasm bytecode for the function declaration belonging to `Context` pub fn gen(self: *Context) InnerError!void { - assert(self.bytes.items.len == 0); + assert(self.code.items.len == 0); try self.genFunctype(); - const writer = self.bytes.writer(); + const writer = self.code.writer(); // Reserve space to write the size after generating the code - try self.bytes.resize(5); + try self.code.resize(5); // Write instructions // TODO: check for and handle death of instructions @@ -170,8 +171,8 @@ pub const Context = struct { // Fill in the size of the generated code to the reserved space at the // beginning of the buffer. - const size = self.bytes.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5; - leb.writeUnsignedFixed(5, self.bytes.items[0..5], @intCast(u32, size)); + const size = self.code.items.len - 5 + self.decl.fn_link.wasm.?.idx_refs.items.len * 5; + leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size)); } fn genInst(self: *Context, inst: *Inst) InnerError!WValue { @@ -198,6 +199,7 @@ pub const Context = struct { } fn genRet(self: *Context, inst: *Inst.UnOp) InnerError!WValue { + // TODO: Implement tail calls const operand = self.resolveInst(inst.operand); try self.emitWValue(operand); return WValue.none; @@ -214,12 +216,12 @@ pub const Context = struct { try self.emitWValue(arg_val); } - try self.bytes.append(0x10); // call + try self.code.append(0x10); // call // The function index immediate argument will be filled in using this data // in link.Wasm.flush(). try self.decl.fn_link.wasm.?.idx_refs.append(self.gpa, .{ - .offset = @intCast(u32, self.bytes.items.len), + .offset = @intCast(u32, self.code.items.len), .decl = target, }); @@ -232,7 +234,7 @@ pub const Context = struct { } fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue { - const writer = self.bytes.writer(); + const writer = self.code.writer(); const lhs = self.resolveInst(inst.lhs); const rhs = self.resolveInst(inst.rhs); @@ -262,12 +264,12 @@ pub const Context = struct { try self.emitWValue(lhs); try self.emitWValue(rhs); - try self.bytes.append(0x6A); // i32.add + try self.code.append(0x6A); // i32.add return WValue.none; } fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void { - const writer = self.bytes.writer(); + const writer = self.code.writer(); switch (inst.base.ty.tag()) { .u32 => { try writer.writeByte(0x41); // i32.const diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 8e3d79d4db..4640c9f1af 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -122,7 +122,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { var context = codegen.Context{ .gpa = self.base.allocator, .values = codegen.ValueTable.init(self.base.allocator), - .bytes = managed_code, + .code = managed_code, .func_type_data = managed_functype, .decl = decl, .err_msg = undefined, @@ -140,7 +140,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { }; fn_data.functype = context.func_type_data.toUnmanaged(); - fn_data.code = context.bytes.toUnmanaged(); + fn_data.code = context.code.toUnmanaged(); } pub fn updateDeclExports( diff --git a/test/stage2/test.zig b/test/stage2/test.zig index f25f07adbf..afe006574f 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -21,17 +21,13 @@ const linux_riscv64 = std.zig.CrossTarget{ .os_tag = .linux, }; -const wasi = std.zig.CrossTarget{ - .cpu_arch = .wasm32, - .os_tag = .wasi, -}; - pub fn addCases(ctx: *TestContext) !void { try @import("cbe.zig").addCases(ctx); try @import("spu-ii.zig").addCases(ctx); try @import("arm.zig").addCases(ctx); try @import("aarch64.zig").addCases(ctx); try @import("llvm.zig").addCases(ctx); + try @import("wasm.zig").addCases(ctx); { var case = ctx.exe("hello world with updates", linux_x64); @@ -1158,62 +1154,6 @@ pub fn addCases(ctx: *TestContext) !void { }); } - { - var case = ctx.exe("wasm function calls", wasi); - - case.addCompareOutput( - \\export fn _start() u32 { - \\ foo(); - \\ bar(); - \\ return 42; - \\} - \\fn foo() void { - \\ bar(); - \\ bar(); - \\} - \\fn bar() void {} - , - "42\n", - ); - - case.addCompareOutput( - \\export fn _start() i64 { - \\ bar(); - \\ foo(); - \\ foo(); - \\ bar(); - \\ foo(); - \\ bar(); - \\ return 42; - \\} - \\fn foo() void { - \\ bar(); - \\} - \\fn bar() void {} - , - "42\n", - ); - - case.addCompareOutput( - \\export fn _start() f32 { - \\ bar(); - \\ foo(); - \\ return 42.0; - \\} - \\fn foo() void { - \\ bar(); - \\ bar(); - \\ bar(); - \\} - \\fn bar() void {} - , - // This is what you get when you take the bits of the IEE-754 - // representation of 42.0 and reinterpret them as an unsigned - // integer. Guess that's a bug in wasmtime. - "1109917696\n", - ); - } - ctx.compileError("function redefinition", linux_x64, \\fn entry() void {} \\fn entry() void {} diff --git a/test/stage2/wasm.zig b/test/stage2/wasm.zig new file mode 100644 index 0000000000..f522db8809 --- /dev/null +++ b/test/stage2/wasm.zig @@ -0,0 +1,125 @@ +const std = @import("std"); +const TestContext = @import("../../src/test.zig").TestContext; + +const wasi = std.zig.CrossTarget{ + .cpu_arch = .wasm32, + .os_tag = .wasi, +}; + +pub fn addCases(ctx: *TestContext) !void { + { + var case = ctx.exe("wasm function calls", wasi); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ foo(); + \\ bar(); + \\ return 42; + \\} + \\fn foo() void { + \\ bar(); + \\ bar(); + \\} + \\fn bar() void {} + , + "42\n", + ); + + case.addCompareOutput( + \\export fn _start() i64 { + \\ bar(); + \\ foo(); + \\ foo(); + \\ bar(); + \\ foo(); + \\ bar(); + \\ return 42; + \\} + \\fn foo() void { + \\ bar(); + \\} + \\fn bar() void {} + , + "42\n", + ); + + case.addCompareOutput( + \\export fn _start() f32 { + \\ bar(); + \\ foo(); + \\ return 42.0; + \\} + \\fn foo() void { + \\ bar(); + \\ bar(); + \\ bar(); + \\} + \\fn bar() void {} + , + // This is what you get when you take the bits of the IEE-754 + // representation of 42.0 and reinterpret them as an unsigned + // integer. Guess that's a bug in wasmtime. + "1109917696\n", + ); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ foo(10, 20); + \\ return 5; + \\} + \\fn foo(x: u32, y: u32) void {} + , "5\n"); + } + + { + var case = ctx.exe("wasm locals", wasi); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 5; + \\ var y: f32 = 42.0; + \\ var x: u32 = 10; + \\ return i; + \\} + , "5\n"); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 5; + \\ var y: f32 = 42.0; + \\ var x: u32 = 10; + \\ foo(i, x); + \\ i = x; + \\ return i; + \\} + \\fn foo(x: u32, y: u32) void { + \\ var i: u32 = 10; + \\ i = x; + \\} + , "10\n"); + } + + { + var case = ctx.exe("wasm binary operands", wasi); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 5; + \\ i += 20; + \\ return i; + \\} + , "25\n"); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 5; + \\ i += 20; + \\ var result: u32 = foo(i, 10); + \\ return result; + \\} + \\fn foo(x: u32, y: u32) u32 { + \\ return x + y; + \\} + , "35\n"); + } +} -- cgit v1.2.3 From 6a87ce0b62a3c9d3a795f3a16b1650f4a8c3b2fc Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sat, 16 Jan 2021 18:22:20 +0100 Subject: Generate correct opcode for 'addGen' depending on type --- src/codegen/wasm.zig | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index b5e044b258..70e9de7baf 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -264,7 +264,15 @@ pub const Context = struct { try self.emitWValue(lhs); try self.emitWValue(rhs); - try self.code.append(0x6A); // i32.add + const opcode: u8 = switch (inst.base.ty.tag()) { + .u32, .i32 => 0x6A, //i32.add + .u64, .i64 => 0x7C, //i64.add + .f32 => 0x92, //f32.add + .f64 => 0xA0, //f64.add + else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}), + }; + + try self.code.append(opcode); return WValue.none; } -- cgit v1.2.3 From fbd5fbe729b7d3f085d2d479ed9957decc019332 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sat, 16 Jan 2021 14:15:40 +0100 Subject: stage2 AArch64: add very basic return values --- src/codegen.zig | 20 +++++++++++++++++--- src/codegen/aarch64.zig | 27 ++++++++++++++------------- 2 files changed, 31 insertions(+), 16 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen.zig b/src/codegen.zig index bfb1540e40..7c67a9191b 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2935,8 +2935,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { ).toU32()); // ldr x28, [sp], #16 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{ - .rn = Register.sp, - .offset = Instruction.LoadStoreOffset.imm_post_index(16), + .register = .{ + .rn = Register.sp, + .offset = Instruction.LoadStoreOffset.imm_post_index(16), + }, }).toU32()); } else { // stp x0, x28, [sp, #-16] @@ -2978,7 +2980,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // The value is in memory at a hard-coded address. // If the type is a pointer, it means the pointer address is at this memory location. try self.genSetReg(src, reg, .{ .immediate = addr }); - mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .rn = reg }).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(reg, .{ .register = .{ .rn = reg } }).toU32()); } }, else => return self.fail(src, "TODO implement genSetReg for aarch64 {}", .{mcv}), @@ -3620,6 +3622,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, else => return self.fail(src, "TODO implement function return values for {}", .{cc}), }, + .aarch64 => switch (cc) { + .Naked => unreachable, + .Unspecified, .C => { + const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); + if (ret_ty_size <= 8) { + result.return_value = .{ .register = c_abi_int_return_regs[0] }; + } else { + return self.fail(src, "TODO support more return types for ARM backend", .{}); + } + }, + else => return self.fail(src, "TODO implement function return values for {}", .{cc}), + }, else => return self.fail(src, "TODO implement codegen return values for {}", .{self.target.cpu.arch}), } return result; diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 50cdf6a262..5fba1ea7e1 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -64,7 +64,7 @@ pub const callee_preserved_regs = [_]Register{ }; pub const c_abi_int_param_regs = [_]Register{ .x0, .x1, .x2, .x3, .x4, .x5, .x6, .x7 }; -pub const c_abi_int_return_regs = [_]Register{ .x0, .x1 }; +pub const c_abi_int_return_regs = [_]Register{ .x0, .x1, .x2, .x3, .x4, .x5, .x6, .x7 }; test "Register.id" { testing.expectEqual(@as(u5, 0), Register.x0.id()); @@ -699,17 +699,18 @@ pub const Instruction = union(enum) { // Load or store register - pub const LdrArgs = struct { - rn: ?Register = null, - offset: LoadStoreOffset = LoadStoreOffset.none, - literal: ?u19 = null, + pub const LdrArgs = union(enum) { + register: struct { + rn: Register, + offset: LoadStoreOffset = LoadStoreOffset.none, + }, + literal: u19, }; pub fn ldr(rt: Register, args: LdrArgs) Instruction { - if (args.rn) |rn| { - return loadStoreRegister(rt, rn, args.offset, true); - } else { - return loadLiteral(rt, args.literal.?); + switch (args) { + .register => |info| return loadStoreRegister(rt, info.rn, info.offset, true), + .literal => |literal| return loadLiteral(rt, literal), } } @@ -911,19 +912,19 @@ test "serialize instructions" { .expected = 0b1_00101_00_0000_0000_0000_0000_0000_0100, }, .{ // ldr x2, [x1] - .inst = Instruction.ldr(.x2, .{ .rn = .x1 }), + .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1 } }), .expected = 0b11_111_0_01_01_000000000000_00001_00010, }, .{ // ldr x2, [x1, #1]! - .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_pre_index(1) }), + .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_pre_index(1) } }), .expected = 0b11_111_0_00_01_0_000000001_11_00001_00010, }, .{ // ldr x2, [x1], #-1 - .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_post_index(-1) }), + .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_post_index(-1) } }), .expected = 0b11_111_0_00_01_0_111111111_01_00001_00010, }, .{ // ldr x2, [x1], (x3) - .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.reg(.x3) }), + .inst = Instruction.ldr(.x2, .{ .register = .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.reg(.x3) } }), .expected = 0b11_111_0_00_01_1_00011_011_0_10_00001_00010, }, .{ // ldr x2, label -- cgit v1.2.3 From d2a297c2b3eb293b393f41640892ff7a5a71027f Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sun, 10 Jan 2021 20:50:38 +0100 Subject: stage2 ARM: add extra load/store instructions --- src/codegen.zig | 76 ++++++++++++++++++++++++++------------- src/codegen/arm.zig | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 25 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen.zig b/src/codegen.zig index 7c67a9191b..709c91a635 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2630,21 +2630,34 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .register => |reg| { const abi_size = ty.abiSize(self.target.*); const adj_off = stack_offset + abi_size; - const offset = if (adj_off <= math.maxInt(u12)) blk: { - break :blk Instruction.Offset.imm(@intCast(u12, adj_off)); - } else Instruction.Offset.reg(try self.copyToTmpRegister(src, MCValue{ .immediate = adj_off }), 0); switch (abi_size) { - 1 => writeInt(u32, try self.code.addManyAsArray(4), Instruction.strb(.al, reg, .fp, .{ - .offset = offset, - .positive = false, - }).toU32()), - 2 => return self.fail(src, "TODO implement strh", .{}), - 4 => writeInt(u32, try self.code.addManyAsArray(4), Instruction.str(.al, reg, .fp, .{ - .offset = offset, - .positive = false, - }).toU32()), - else => return self.fail(src, "TODO a type of size {} is not allowed in a register", .{abi_size}), + 1, 4 => { + const offset = if (adj_off <= math.maxInt(u12)) blk: { + break :blk Instruction.Offset.imm(@intCast(u12, adj_off)); + } else Instruction.Offset.reg(try self.copyToTmpRegister(src, MCValue{ .immediate = adj_off }), 0); + const str = switch (abi_size) { + 1 => Instruction.strb, + 4 => Instruction.str, + else => unreachable, + }; + + writeInt(u32, try self.code.addManyAsArray(4), str(.al, reg, .fp, .{ + .offset = offset, + .positive = false, + }).toU32()); + }, + 2 => { + const offset = if (adj_off <= math.maxInt(u8)) blk: { + break :blk Instruction.ExtraLoadStoreOffset.imm(@intCast(u8, adj_off)); + } else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(src, MCValue{ .immediate = adj_off })); + + writeInt(u32, try self.code.addManyAsArray(4), Instruction.strh(.al, reg, .fp, .{ + .offset = offset, + .positive = false, + }).toU32()); + }, + else => return self.fail(src, "TODO implement storing other types abi_size={}", .{abi_size}), } }, .memory => |vaddr| { @@ -2836,20 +2849,33 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // const abi_size = ty.abiSize(self.target.*); const abi_size = 4; const adj_off = unadjusted_off + abi_size; - const offset = if (adj_off <= math.maxInt(u12)) blk: { - break :blk Instruction.Offset.imm(@intCast(u12, adj_off)); - } else Instruction.Offset.reg(try self.copyToTmpRegister(src, MCValue{ .immediate = adj_off }), 0); switch (abi_size) { - 1 => writeInt(u32, try self.code.addManyAsArray(4), Instruction.ldrb(.al, reg, .fp, .{ - .offset = offset, - .positive = false, - }).toU32()), - 2 => return self.fail(src, "TODO implement ldrh", .{}), - 4 => writeInt(u32, try self.code.addManyAsArray(4), Instruction.ldr(.al, reg, .fp, .{ - .offset = offset, - .positive = false, - }).toU32()), + 1, 4 => { + const offset = if (adj_off <= math.maxInt(u12)) blk: { + break :blk Instruction.Offset.imm(@intCast(u12, adj_off)); + } else Instruction.Offset.reg(try self.copyToTmpRegister(src, MCValue{ .immediate = adj_off }), 0); + const ldr = switch (abi_size) { + 1 => Instruction.ldrb, + 4 => Instruction.ldr, + else => unreachable, + }; + + writeInt(u32, try self.code.addManyAsArray(4), ldr(.al, reg, .fp, .{ + .offset = offset, + .positive = false, + }).toU32()); + }, + 2 => { + const offset = if (adj_off <= math.maxInt(u8)) blk: { + break :blk Instruction.ExtraLoadStoreOffset.imm(@intCast(u8, adj_off)); + } else Instruction.ExtraLoadStoreOffset.reg(try self.copyToTmpRegister(src, MCValue{ .immediate = adj_off })); + + writeInt(u32, try self.code.addManyAsArray(4), Instruction.ldrh(.al, reg, .fp, .{ + .offset = offset, + .positive = false, + }).toU32()); + }, else => return self.fail(src, "TODO a type of size {} is not allowed in a register", .{abi_size}), } }, diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig index 978c653cb0..94f1ae951d 100644 --- a/src/codegen/arm.zig +++ b/src/codegen/arm.zig @@ -240,6 +240,22 @@ pub const Instruction = union(enum) { fixed: u2 = 0b01, cond: u4, }, + ExtraLoadStore: packed struct { + imm4l: u4, + fixed_1: u1 = 0b1, + op2: u2, + fixed_2: u1 = 0b1, + imm4h: u4, + rt: u4, + rn: u4, + o1: u1, + write_back: u1, + imm: u1, + up_down: u1, + pre_index: u1, + fixed_3: u3 = 0b000, + cond: u4, + }, BlockDataTransfer: packed struct { register_list: u16, rn: u4, @@ -468,6 +484,29 @@ pub const Instruction = union(enum) { } }; + /// Represents the offset operand of an extra load or store + /// instruction. + pub const ExtraLoadStoreOffset = union(enum) { + immediate: u8, + register: u4, + + pub const none = ExtraLoadStoreOffset{ + .immediate = 0, + }; + + pub fn reg(register: Register) ExtraLoadStoreOffset { + return ExtraLoadStoreOffset{ + .register = register.id(), + }; + } + + pub fn imm(immediate: u8) ExtraLoadStoreOffset { + return ExtraLoadStoreOffset{ + .immediate = immediate, + }; + } + }; + /// Represents the register list operand to a block data transfer /// instruction pub const RegisterList = packed struct { @@ -495,6 +534,7 @@ pub const Instruction = union(enum) { .Multiply => |v| @bitCast(u32, v), .MultiplyLong => |v| @bitCast(u32, v), .SingleDataTransfer => |v| @bitCast(u32, v), + .ExtraLoadStore => |v| @bitCast(u32, v), .BlockDataTransfer => |v| @bitCast(u32, v), .Branch => |v| @bitCast(u32, v), .BranchExchange => |v| @bitCast(u32, v), @@ -617,6 +657,43 @@ pub const Instruction = union(enum) { }; } + fn extraLoadStore( + cond: Condition, + pre_index: bool, + positive: bool, + write_back: bool, + o1: u1, + op2: u2, + rn: Register, + rt: Register, + offset: ExtraLoadStoreOffset, + ) Instruction { + const imm4l: u4 = switch (offset) { + .immediate => |imm| @truncate(u4, imm), + .register => |reg| reg, + }; + const imm4h: u4 = switch (offset) { + .immediate => |imm| @truncate(u4, imm >> 4), + .register => |reg| 0b0000, + }; + + return Instruction{ + .ExtraLoadStore = .{ + .imm4l = imm4l, + .op2 = op2, + .imm4h = imm4h, + .rt = rt.id(), + .rn = rn.id(), + .o1 = o1, + .write_back = @boolToInt(write_back), + .imm = @boolToInt(offset == .immediate), + .up_down = @boolToInt(positive), + .pre_index = @boolToInt(pre_index), + .cond = @enumToInt(cond), + }, + }; + } + fn blockDataTransfer( cond: Condition, rn: Register, @@ -913,6 +990,23 @@ pub const Instruction = union(enum) { return singleDataTransfer(cond, rd, rn, args.offset, args.pre_index, args.positive, 1, args.write_back, 0); } + // Extra load/store + + pub const ExtraLoadStoreOffsetArgs = struct { + pre_index: bool = true, + positive: bool = true, + offset: ExtraLoadStoreOffset, + write_back: bool = false, + }; + + pub fn strh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction { + return extraLoadStore(cond, args.pre_index, args.positive, args.write_back, 0, 0b01, rn, rt, args.offset); + } + + pub fn ldrh(cond: Condition, rt: Register, rn: Register, args: ExtraLoadStoreOffsetArgs) Instruction { + return extraLoadStore(cond, args.pre_index, args.positive, args.write_back, 1, 0b01, rn, rt, args.offset); + } + // Block data transfer pub fn ldmda(cond: Condition, rn: Register, write_back: bool, reg_list: RegisterList) Instruction { @@ -1093,6 +1187,12 @@ test "serialize instructions" { }), .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000, }, + .{ // strh r1, [r5] + .inst = Instruction.strh(.al, .r1, .r5, .{ + .offset = Instruction.ExtraLoadStoreOffset.none, + }), + .expected = 0b1110_000_1_1_1_0_0_0101_0001_0000_1011_0000, + }, .{ // b #12 .inst = Instruction.b(.al, 12), .expected = 0b1110_101_0_0000_0000_0000_0000_0000_0011, -- cgit v1.2.3 From 8c9ac4db978c80246b4872c899b1618b1b195ec2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 16 Jan 2021 22:51:01 -0700 Subject: stage2: implement error notes and regress -femit-zir * Implement error notes - note: other symbol exported here - note: previous else prong is here - note: previous '_' prong is here * Add Compilation.CObject.ErrorMsg. This object properly converts to AllErrors.Message when the time comes. * Add Compilation.CObject.failure_retryable. Properly handles out-of-memory and other transient failures. * Introduce Module.SrcLoc which has not only a byte offset but also references the file which the byte offset applies to. * Scope.Block now contains both a pointer to the "owner" Decl and the "source" Decl. As an example, during inline function call, the "owner" will be the Decl of the caller and the "source" will be the Decl of the callee. * Module.ErrorMsg now sports a `file_scope` field so that notes can refer to source locations in a file other than the parent error message. * Some instances where a `*Scope` was stored, now store a `*Scope.Container`. * Some methods in the `Scope` namespace were moved to the more specific type, since there was only an implementation for one particular tag. - `removeDecl` moved to `Scope.Container` - `destroy` moved to `Scope.File` * Two kinds of Scope deleted: - zir_module - decl * astgen: properly use DeclVal / DeclRef. DeclVal was incorrectly changed to be a reference; this commit fixes it. Fewer ZIR instructions processed as a result. - declval_in_module is renamed to declval - previous declval ZIR instruction is deleted; it was only for .zir files. * Test harness: friendlier diagnostics when an unexpected set of errors is encountered. * zir_sema: fix analyzeInstBlockFlat by properly calling resolvingInst on the last zir instruction in the block. Compile log implementation: * Write to a buffer rather than directly to stderr. * Only keep track of 1 callsite per Decl. * No longer mutate the ZIR Inst struct data. * "Compile log statement found" errors are only emitted when there are no other compile errors. -femit-zir and support for .zir source files is regressed. If we wanted to support this again, outputting .zir would need to be done as yet another backend rather than in the haphazard way it was previously implemented. For parsing .zir, it was implemented previously in a way that was not helpful for debugging. We need tighter integration with the test harness for it to be useful; so clearly a rewrite is needed. Given that a rewrite is needed, and it was getting in the way of progress and organization of the rest of stage2, I regressed the feature. --- src/Compilation.zig | 305 +++++--- src/Module.zig | 764 +++++++------------ src/astgen.zig | 77 +- src/codegen.zig | 166 ++-- src/codegen/c.zig | 7 +- src/codegen/llvm.zig | 13 +- src/link/Coff.zig | 6 +- src/link/Elf.zig | 33 +- src/link/MachO.zig | 8 +- src/link/MachO/DebugSymbols.zig | 27 +- src/main.zig | 47 +- src/test.zig | 212 +++--- src/type/Enum.zig | 2 +- src/type/Struct.zig | 2 +- src/type/Union.zig | 2 +- src/zir.zig | 1610 +-------------------------------------- src/zir_sema.zig | 179 ++--- test/stage2/test.zig | 62 +- 18 files changed, 904 insertions(+), 2618 deletions(-) (limited to 'src/codegen') diff --git a/src/Compilation.zig b/src/Compilation.zig index 225a91e5d2..ad99e40541 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -51,7 +51,7 @@ c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic), /// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator. /// This data is accessed by multiple threads and is protected by `mutex`. -failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *ErrorMsg) = .{}, +failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *CObject.ErrorMsg) = .{}, keep_source_files_loaded: bool, use_clang: bool, @@ -215,13 +215,29 @@ pub const CObject = struct { }, /// There will be a corresponding ErrorMsg in Compilation.failed_c_objects. failure, + /// A transient failure happened when trying to compile the C Object; it may + /// succeed if we try again. There may be a corresponding ErrorMsg in + /// Compilation.failed_c_objects. If there is not, the failure is out of memory. + failure_retryable, }, + pub const ErrorMsg = struct { + msg: []const u8, + line: u32, + column: u32, + + pub fn destroy(em: *ErrorMsg, gpa: *Allocator) void { + gpa.free(em.msg); + gpa.destroy(em); + em.* = undefined; + } + }; + /// Returns if there was failure. pub fn clearStatus(self: *CObject, gpa: *Allocator) bool { switch (self.status) { .new => return false, - .failure => { + .failure, .failure_retryable => { self.status = .new; return true; }, @@ -240,6 +256,11 @@ pub const CObject = struct { } }; +/// To support incremental compilation, errors are stored in various places +/// so that they can be created and destroyed appropriately. This structure +/// is used to collect all the errors from the various places into one +/// convenient place for API users to consume. It is allocated into 1 heap +/// and freed all at once. pub const AllErrors = struct { arena: std.heap.ArenaAllocator.State, list: []const Message, @@ -251,23 +272,32 @@ pub const AllErrors = struct { column: usize, byte_offset: usize, msg: []const u8, + notes: []Message = &.{}, }, plain: struct { msg: []const u8, }, - pub fn renderToStdErr(self: Message) void { - switch (self) { + pub fn renderToStdErr(msg: Message) void { + return msg.renderToStdErrInner("error"); + } + + fn renderToStdErrInner(msg: Message, kind: []const u8) void { + switch (msg) { .src => |src| { - std.debug.print("{s}:{d}:{d}: error: {s}\n", .{ + std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{ src.src_path, src.line + 1, src.column + 1, + kind, src.msg, }); + for (src.notes) |note| { + note.renderToStdErrInner("note"); + } }, .plain => |plain| { - std.debug.print("error: {s}\n", .{plain.msg}); + std.debug.print("{s}: {s}\n", .{ kind, plain.msg }); }, } } @@ -278,20 +308,38 @@ pub const AllErrors = struct { } fn add( + module: *Module, arena: *std.heap.ArenaAllocator, errors: *std.ArrayList(Message), - sub_file_path: []const u8, - source: []const u8, - simple_err_msg: ErrorMsg, + module_err_msg: Module.ErrorMsg, ) !void { - const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset); + const notes = try arena.allocator.alloc(Message, module_err_msg.notes.len); + for (notes) |*note, i| { + const module_note = module_err_msg.notes[i]; + const source = try module_note.src_loc.file_scope.getSource(module); + const loc = std.zig.findLineColumn(source, module_note.src_loc.byte_offset); + const sub_file_path = module_note.src_loc.file_scope.sub_file_path; + note.* = .{ + .src = .{ + .src_path = try arena.allocator.dupe(u8, sub_file_path), + .msg = try arena.allocator.dupe(u8, module_note.msg), + .byte_offset = module_note.src_loc.byte_offset, + .line = loc.line, + .column = loc.column, + }, + }; + } + const source = try module_err_msg.src_loc.file_scope.getSource(module); + const loc = std.zig.findLineColumn(source, module_err_msg.src_loc.byte_offset); + const sub_file_path = module_err_msg.src_loc.file_scope.sub_file_path; try errors.append(.{ .src = .{ .src_path = try arena.allocator.dupe(u8, sub_file_path), - .msg = try arena.allocator.dupe(u8, simple_err_msg.msg), - .byte_offset = simple_err_msg.byte_offset, + .msg = try arena.allocator.dupe(u8, module_err_msg.msg), + .byte_offset = module_err_msg.src_loc.byte_offset, .line = loc.line, .column = loc.column, + .notes = notes, }, }); } @@ -849,17 +897,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .ty = struct_ty, }, }; - break :rs &root_scope.base; + break :rs root_scope; } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) { - const root_scope = try gpa.create(Module.Scope.ZIRModule); - root_scope.* = .{ - .sub_file_path = root_pkg.root_src_path, - .source = .{ .unloaded = {} }, - .contents = .{ .not_available = {} }, - .status = .never_loaded, - .decls = .{}, - }; - break :rs &root_scope.base; + return error.ZirFilesUnsupported; } else { unreachable; } @@ -1258,32 +1298,23 @@ pub fn update(self: *Compilation) !void { const use_stage1 = build_options.is_stage1 and self.bin_file.options.use_llvm; if (!use_stage1) { if (self.bin_file.options.module) |module| { + module.compile_log_text.shrinkAndFree(module.gpa, 0); module.generation += 1; // TODO Detect which source files changed. // Until then we simulate a full cache miss. Source files could have been loaded for any reason; // to force a refresh we unload now. - if (module.root_scope.cast(Module.Scope.File)) |zig_file| { - zig_file.unload(module.gpa); - module.failed_root_src_file = null; - module.analyzeContainer(&zig_file.root_container) catch |err| switch (err) { - error.AnalysisFail => { - assert(self.totalErrorCount() != 0); - }, - error.OutOfMemory => return error.OutOfMemory, - else => |e| { - module.failed_root_src_file = e; - }, - }; - } else if (module.root_scope.cast(Module.Scope.ZIRModule)) |zir_module| { - zir_module.unload(module.gpa); - module.analyzeRootZIRModule(zir_module) catch |err| switch (err) { - error.AnalysisFail => { - assert(self.totalErrorCount() != 0); - }, - else => |e| return e, - }; - } + module.root_scope.unload(module.gpa); + module.failed_root_src_file = null; + module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) { + error.AnalysisFail => { + assert(self.totalErrorCount() != 0); + }, + error.OutOfMemory => return error.OutOfMemory, + else => |e| { + module.failed_root_src_file = e; + }, + }; // TODO only analyze imports if they are still referenced for (module.import_table.items()) |entry| { @@ -1359,14 +1390,18 @@ pub fn totalErrorCount(self: *Compilation) usize { module.failed_exports.items().len + module.failed_files.items().len + @boolToInt(module.failed_root_src_file != null); - for (module.compile_log_decls.items()) |entry| { - total += entry.value.items.len; - } } // The "no entry point found" error only counts if there are no other errors. if (total == 0) { - return @boolToInt(self.link_error_flags.no_entry_point_found); + total += @boolToInt(self.link_error_flags.no_entry_point_found); + } + + // Compile log errors only count if there are no other errors. + if (total == 0) { + if (self.bin_file.options.module) |module| { + total += @boolToInt(module.compile_log_decls.items().len != 0); + } } return total; @@ -1382,32 +1417,32 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { for (self.failed_c_objects.items()) |entry| { const c_object = entry.key; const err_msg = entry.value; - try AllErrors.add(&arena, &errors, c_object.src.src_path, "", err_msg.*); + // TODO these fields will need to be adjusted when we have proper + // C error reporting bubbling up. + try errors.append(.{ + .src = .{ + .src_path = try arena.allocator.dupe(u8, c_object.src.src_path), + .msg = try std.fmt.allocPrint(&arena.allocator, "unable to build C object: {s}", .{ + err_msg.msg, + }), + .byte_offset = 0, + .line = err_msg.line, + .column = err_msg.column, + }, + }); } if (self.bin_file.options.module) |module| { for (module.failed_files.items()) |entry| { - const scope = entry.key; - const err_msg = entry.value; - const source = try scope.getSource(module); - try AllErrors.add(&arena, &errors, scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.failed_decls.items()) |entry| { - const decl = entry.key; - const err_msg = entry.value; - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.emit_h_failed_decls.items()) |entry| { - const decl = entry.key; - const err_msg = entry.value; - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.failed_exports.items()) |entry| { - const decl = entry.key.owner_decl; - const err_msg = entry.value; - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } if (module.failed_root_src_file) |err| { const file_path = try module.root_pkg.root_src_directory.join(&arena.allocator, &[_][]const u8{ @@ -1418,15 +1453,6 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }); try AllErrors.addPlain(&arena, &errors, msg); } - for (module.compile_log_decls.items()) |entry| { - const decl = entry.key; - const path = decl.scope.subFilePath(); - const source = try decl.scope.getSource(module); - for (entry.value.items) |src_loc| { - const err_msg = ErrorMsg{ .byte_offset = src_loc, .msg = "found compile log statement" }; - try AllErrors.add(&arena, &errors, path, source, err_msg); - } - } } if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) { @@ -1437,6 +1463,28 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }); } + if (self.bin_file.options.module) |module| { + const compile_log_items = module.compile_log_decls.items(); + if (errors.items.len == 0 and compile_log_items.len != 0) { + // First one will be the error; subsequent ones will be notes. + const err_msg = Module.ErrorMsg{ + .src_loc = compile_log_items[0].value, + .msg = "found compile log statement", + .notes = try self.gpa.alloc(Module.ErrorMsg, compile_log_items.len - 1), + }; + defer self.gpa.free(err_msg.notes); + + for (compile_log_items[1..]) |entry, i| { + err_msg.notes[i] = .{ + .src_loc = entry.value, + .msg = "also here", + }; + } + + try AllErrors.add(module, &arena, &errors, err_msg); + } + } + assert(errors.items.len == self.totalErrorCount()); return AllErrors{ @@ -1445,6 +1493,11 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }; } +pub fn getCompileLogOutput(self: *Compilation) []const u8 { + const module = self.bin_file.options.module orelse return &[0]u8{}; + return module.compile_log_text.items; +} + pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void { var progress: std.Progress = .{}; var main_progress_node = try progress.start("", 0); @@ -1517,9 +1570,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor }, else => { try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create( module.gpa, - decl.src(), + decl.srcLoc(), "unable to codegen: {s}", .{@errorName(err)}, )); @@ -1586,9 +1639,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor const module = self.bin_file.options.module.?; self.bin_file.updateDeclLineNumber(module, decl) catch |err| { try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create( module.gpa, - decl.src(), + decl.srcLoc(), "unable to update line number: {s}", .{@errorName(err)}, )); @@ -1858,26 +1911,38 @@ fn workerUpdateCObject( comp.updateCObject(c_object, progress_node) catch |err| switch (err) { error.AnalysisFail => return, else => { - { - const lock = comp.mutex.acquire(); - defer lock.release(); - comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1) catch { - fatal("TODO handle this by setting c_object.status = oom failure", .{}); - }; - comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, ErrorMsg.create( - comp.gpa, - 0, - "unable to build C object: {s}", - .{@errorName(err)}, - ) catch { - fatal("TODO handle this by setting c_object.status = oom failure", .{}); - }); - } - c_object.status = .{ .failure = {} }; + comp.reportRetryableCObjectError(c_object, err) catch |oom| switch (oom) { + // Swallowing this error is OK because it's implied to be OOM when + // there is a missing failed_c_objects error message. + error.OutOfMemory => {}, + }; }, }; } +fn reportRetryableCObjectError( + comp: *Compilation, + c_object: *CObject, + err: anyerror, +) error{OutOfMemory}!void { + c_object.status = .failure_retryable; + + const c_obj_err_msg = try comp.gpa.create(CObject.ErrorMsg); + errdefer comp.gpa.destroy(c_obj_err_msg); + const msg = try std.fmt.allocPrint(comp.gpa, "unable to build C object: {s}", .{@errorName(err)}); + errdefer comp.gpa.free(msg); + c_obj_err_msg.* = .{ + .msg = msg, + .line = 0, + .column = 0, + }; + { + const lock = comp.mutex.acquire(); + defer lock.release(); + try comp.failed_c_objects.putNoClobber(comp.gpa, c_object, c_obj_err_msg); + } +} + fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return comp.failCObj(c_object, "clang not available: compiler built without LLVM extensions", .{}); @@ -1892,7 +1957,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * // There was previous failure. const lock = comp.mutex.acquire(); defer lock.release(); - comp.failed_c_objects.removeAssertDiscard(c_object); + // If the failure was OOM, there will not be an entry here, so we do + // not assert discard. + _ = comp.failed_c_objects.swapRemove(c_object); } var man = comp.obtainCObjectCacheManifest(); @@ -2343,11 +2410,27 @@ pub fn addCCArgs( fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8, args: anytype) InnerError { @setCold(true); - const err_msg = try ErrorMsg.create(comp.gpa, 0, "unable to build C object: " ++ format, args); + const err_msg = blk: { + const msg = try std.fmt.allocPrint(comp.gpa, format, args); + errdefer comp.gpa.free(msg); + const err_msg = try comp.gpa.create(CObject.ErrorMsg); + errdefer comp.gpa.destroy(err_msg); + err_msg.* = .{ + .msg = msg, + .line = 0, + .column = 0, + }; + break :blk err_msg; + }; return comp.failCObjWithOwnedErrorMsg(c_object, err_msg); } -fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *ErrorMsg) InnerError { +fn failCObjWithOwnedErrorMsg( + comp: *Compilation, + c_object: *CObject, + err_msg: *CObject.ErrorMsg, +) InnerError { + @setCold(true); { const lock = comp.mutex.acquire(); defer lock.release(); @@ -2361,36 +2444,6 @@ fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *E return error.AnalysisFail; } -pub const ErrorMsg = struct { - byte_offset: usize, - msg: []const u8, - - pub fn create(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !*ErrorMsg { - const self = try gpa.create(ErrorMsg); - errdefer gpa.destroy(self); - self.* = try init(gpa, byte_offset, format, args); - return self; - } - - /// Assumes the ErrorMsg struct and msg were both allocated with allocator. - pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void { - self.deinit(gpa); - gpa.destroy(self); - } - - pub fn init(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !ErrorMsg { - return ErrorMsg{ - .byte_offset = byte_offset, - .msg = try std.fmt.allocPrint(gpa, format, args), - }; - } - - pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void { - gpa.free(self.msg); - self.* = undefined; - } -}; - pub const FileExt = enum { c, cpp, diff --git a/src/Module.zig b/src/Module.zig index 0bdeab68d0..e612f8f759 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -35,8 +35,7 @@ zig_cache_artifact_directory: Compilation.Directory, /// Pointer to externally managed resource. `null` if there is no zig file being compiled. root_pkg: *Package, /// Module owns this resource. -/// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`. -root_scope: *Scope, +root_scope: *Scope.File, /// It's rare for a decl to be exported, so we save memory by having a sparse map of /// Decl pointers to details about them being exported. /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. @@ -57,19 +56,19 @@ decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_has /// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator. /// Note that a Decl can succeed but the Fn it represents can fail. In this case, /// a Decl can have a failed_decls entry but have analysis status of success. -failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, +failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{}, /// When emit_h is non-null, each Decl gets one more compile error slot for /// emit-h failing for that Decl. This table is also how we tell if a Decl has /// failed emit-h or succeeded. -emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, -/// A Decl can have multiple compileLogs, but only one error, so we map a Decl to a the src locs of all the compileLogs -compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, ArrayListUnmanaged(usize)) = .{}, +emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *ErrorMsg) = .{}, +/// Keep track of one `@compileLog` callsite per owner Decl. +compile_log_decls: std.AutoArrayHashMapUnmanaged(*Decl, SrcLoc) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. -failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, +failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *ErrorMsg) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Export`, using Module's general purpose allocator. -failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *Compilation.ErrorMsg) = .{}, +failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{}, next_anon_name_index: usize = 0, @@ -103,6 +102,8 @@ stage1_flags: packed struct { emit_h: ?Compilation.EmitLoc, +compile_log_text: std.ArrayListUnmanaged(u8) = .{}, + pub const Export = struct { options: std.builtin.ExportOptions, /// Byte offset into the file that contains the export directive. @@ -138,9 +139,9 @@ pub const Decl = struct { /// mapping them to an address in the output file. /// Memory owned by this decl, using Module's allocator. name: [*:0]const u8, - /// The direct parent container of the Decl. This is either a `Scope.Container` or `Scope.ZIRModule`. + /// The direct parent container of the Decl. /// Reference to externally owned memory. - scope: *Scope, + container: *Scope.Container, /// The AST Node decl index or ZIR Inst index that contains this declaration. /// Must be recomputed when the corresponding source file is modified. src_index: usize, @@ -235,31 +236,21 @@ pub const Decl = struct { } } + pub fn srcLoc(self: Decl) SrcLoc { + return .{ + .byte_offset = self.src(), + .file_scope = self.getFileScope(), + }; + } + pub fn src(self: Decl) usize { - switch (self.scope.tag) { - .container => { - const container = @fieldParentPtr(Scope.Container, "base", self.scope); - const tree = container.file_scope.contents.tree; - // TODO Container should have its own decls() - const decl_node = tree.root_node.decls()[self.src_index]; - return tree.token_locs[decl_node.firstToken()].start; - }, - .zir_module => { - const zir_module = @fieldParentPtr(Scope.ZIRModule, "base", self.scope); - const module = zir_module.contents.module; - const src_decl = module.decls[self.src_index]; - return src_decl.inst.src; - }, - .file, .block => unreachable, - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .decl => unreachable, - } + const tree = self.container.file_scope.contents.tree; + const decl_node = tree.root_node.decls()[self.src_index]; + return tree.token_locs[decl_node.firstToken()].start; } pub fn fullyQualifiedNameHash(self: Decl) Scope.NameHash { - return self.scope.fullyQualifiedNameHash(mem.spanZ(self.name)); + return self.container.fullyQualifiedNameHash(mem.spanZ(self.name)); } pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue { @@ -293,9 +284,8 @@ pub const Decl = struct { } } - /// Asserts that the `Decl` is part of AST and not ZIRModule. - pub fn getFileScope(self: *Decl) *Scope.File { - return self.scope.cast(Scope.Container).?.file_scope; + pub fn getFileScope(self: Decl) *Scope.File { + return self.container.file_scope; } pub fn getEmitH(decl: *Decl, module: *Module) *EmitH { @@ -326,7 +316,7 @@ pub const Fn = struct { /// Contains un-analyzed ZIR instructions generated from Zig source AST. /// Even after we finish analysis, the ZIR is kept in memory, so that /// comptime and inline function calls can happen. - zir: zir.Module.Body, + zir: zir.Body, /// undefined unless analysis state is `success`. body: Body, state: Analysis, @@ -373,47 +363,49 @@ pub const Scope = struct { return @fieldParentPtr(T, "base", base); } - /// Asserts the scope has a parent which is a DeclAnalysis and - /// returns the arena Allocator. + /// Returns the arena Allocator associated with the Decl of the Scope. pub fn arena(self: *Scope) *Allocator { switch (self.tag) { .block => return self.cast(Block).?.arena, - .decl => return &self.cast(DeclAnalysis).?.arena.allocator, .gen_zir => return self.cast(GenZIR).?.arena, .local_val => return self.cast(LocalVal).?.gen_zir.arena, .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena, - .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, .file => unreachable, .container => unreachable, } } - /// If the scope has a parent which is a `DeclAnalysis`, - /// returns the `Decl`, otherwise returns `null`. - pub fn decl(self: *Scope) ?*Decl { + pub fn ownerDecl(self: *Scope) ?*Decl { + return switch (self.tag) { + .block => self.cast(Block).?.owner_decl, + .gen_zir => self.cast(GenZIR).?.decl, + .local_val => self.cast(LocalVal).?.gen_zir.decl, + .local_ptr => self.cast(LocalPtr).?.gen_zir.decl, + .file => null, + .container => null, + }; + } + + pub fn srcDecl(self: *Scope) ?*Decl { return switch (self.tag) { - .block => self.cast(Block).?.decl, + .block => self.cast(Block).?.src_decl, .gen_zir => self.cast(GenZIR).?.decl, .local_val => self.cast(LocalVal).?.gen_zir.decl, .local_ptr => self.cast(LocalPtr).?.gen_zir.decl, - .decl => self.cast(DeclAnalysis).?.decl, - .zir_module => null, .file => null, .container => null, }; } - /// Asserts the scope has a parent which is a ZIRModule or Container and - /// returns it. - pub fn namespace(self: *Scope) *Scope { + /// Asserts the scope has a parent which is a Container and returns it. + pub fn namespace(self: *Scope) *Container { switch (self.tag) { - .block => return self.cast(Block).?.decl.scope, - .gen_zir => return self.cast(GenZIR).?.decl.scope, - .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope, - .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope, - .decl => return self.cast(DeclAnalysis).?.decl.scope, - .file => return &self.cast(File).?.root_container.base, - .zir_module, .container => return self, + .block => return self.cast(Block).?.owner_decl.container, + .gen_zir => return self.cast(GenZIR).?.decl.container, + .local_val => return self.cast(LocalVal).?.gen_zir.decl.container, + .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.container, + .file => return &self.cast(File).?.root_container, + .container => return self.cast(Container).?, } } @@ -426,9 +418,7 @@ pub const Scope = struct { .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, - .decl => unreachable, .file => unreachable, - .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name), .container => return self.cast(Container).?.fullyQualifiedNameHash(name), } } @@ -437,12 +427,10 @@ pub const Scope = struct { pub fn tree(self: *Scope) *ast.Tree { switch (self.tag) { .file => return self.cast(File).?.contents.tree, - .zir_module => unreachable, - .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(Container).?.file_scope.contents.tree, - .block => return self.cast(Block).?.decl.scope.cast(Container).?.file_scope.contents.tree, - .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(Container).?.file_scope.contents.tree, - .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree, - .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(Container).?.file_scope.contents.tree, + .block => return self.cast(Block).?.src_decl.container.file_scope.contents.tree, + .gen_zir => return self.cast(GenZIR).?.decl.container.file_scope.contents.tree, + .local_val => return self.cast(LocalVal).?.gen_zir.decl.container.file_scope.contents.tree, + .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.container.file_scope.contents.tree, .container => return self.cast(Container).?.file_scope.contents.tree, } } @@ -454,38 +442,21 @@ pub const Scope = struct { .gen_zir => self.cast(GenZIR).?, .local_val => return self.cast(LocalVal).?.gen_zir, .local_ptr => return self.cast(LocalPtr).?.gen_zir, - .decl => unreachable, - .zir_module => unreachable, .file => unreachable, .container => unreachable, }; } - /// Asserts the scope has a parent which is a ZIRModule, Container or File and + /// Asserts the scope has a parent which is a Container or File and /// returns the sub_file_path field. pub fn subFilePath(base: *Scope) []const u8 { switch (base.tag) { .container => return @fieldParentPtr(Container, "base", base).file_scope.sub_file_path, .file => return @fieldParentPtr(File, "base", base).sub_file_path, - .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path, .block => unreachable, .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, - .decl => unreachable, - } - } - - pub fn unload(base: *Scope, gpa: *Allocator) void { - switch (base.tag) { - .file => return @fieldParentPtr(File, "base", base).unload(gpa), - .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(gpa), - .block => unreachable, - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .decl => unreachable, - .container => unreachable, } } @@ -493,67 +464,28 @@ pub const Scope = struct { switch (base.tag) { .container => return @fieldParentPtr(Container, "base", base).file_scope.getSource(module), .file => return @fieldParentPtr(File, "base", base).getSource(module), - .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module), .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, .block => unreachable, - .decl => unreachable, } } + /// When called from inside a Block Scope, chases the src_decl, not the owner_decl. pub fn getFileScope(base: *Scope) *Scope.File { var cur = base; while (true) { cur = switch (cur.tag) { .container => return @fieldParentPtr(Container, "base", cur).file_scope, .file => return @fieldParentPtr(File, "base", cur), - .zir_module => unreachable, // TODO are zir modules allowed to import packages? .gen_zir => @fieldParentPtr(GenZIR, "base", cur).parent, .local_val => @fieldParentPtr(LocalVal, "base", cur).parent, .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent, - .block => @fieldParentPtr(Block, "base", cur).decl.scope, - .decl => @fieldParentPtr(DeclAnalysis, "base", cur).decl.scope, + .block => return @fieldParentPtr(Block, "base", cur).src_decl.container.file_scope, }; } } - /// Asserts the scope is a namespace Scope and removes the Decl from the namespace. - pub fn removeDecl(base: *Scope, child: *Decl) void { - switch (base.tag) { - .container => return @fieldParentPtr(Container, "base", base).removeDecl(child), - .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child), - .file => unreachable, - .block => unreachable, - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .decl => unreachable, - } - } - - /// Asserts the scope is a File or ZIRModule and deinitializes it, then deallocates it. - pub fn destroy(base: *Scope, gpa: *Allocator) void { - switch (base.tag) { - .file => { - const scope_file = @fieldParentPtr(File, "base", base); - scope_file.deinit(gpa); - gpa.destroy(scope_file); - }, - .zir_module => { - const scope_zir_module = @fieldParentPtr(ZIRModule, "base", base); - scope_zir_module.deinit(gpa); - gpa.destroy(scope_zir_module); - }, - .block => unreachable, - .gen_zir => unreachable, - .local_val => unreachable, - .local_ptr => unreachable, - .decl => unreachable, - .container => unreachable, - } - } - fn name_hash_hash(x: NameHash) u32 { return @truncate(u32, @bitCast(u128, x)); } @@ -563,14 +495,11 @@ pub const Scope = struct { } pub const Tag = enum { - /// .zir source code. - zir_module, /// .zig source code. file, /// struct, enum or union, every .file contains one of these. container, block, - decl, gen_zir, local_val, local_ptr, @@ -657,6 +586,11 @@ pub const Scope = struct { self.* = undefined; } + pub fn destroy(self: *File, gpa: *Allocator) void { + self.deinit(gpa); + gpa.destroy(self); + } + pub fn dumpSrc(self: *File, src: usize) void { const loc = std.zig.findLineColumn(self.source.bytes, src); std.debug.print("{s}:{d}:{d}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); @@ -681,109 +615,6 @@ pub const Scope = struct { } }; - pub const ZIRModule = struct { - pub const base_tag: Tag = .zir_module; - base: Scope = Scope{ .tag = base_tag }, - /// Relative to the owning package's root_src_dir. - /// Reference to external memory, not owned by ZIRModule. - sub_file_path: []const u8, - source: union(enum) { - unloaded: void, - bytes: [:0]const u8, - }, - contents: union { - not_available: void, - module: *zir.Module, - }, - status: enum { - never_loaded, - unloaded_success, - unloaded_parse_failure, - unloaded_sema_failure, - - loaded_sema_failure, - loaded_success, - }, - - /// Even though .zir files only have 1 module, this set is still needed - /// because of anonymous Decls, which can exist in the global set, but - /// not this one. - decls: ArrayListUnmanaged(*Decl), - - pub fn unload(self: *ZIRModule, gpa: *Allocator) void { - switch (self.status) { - .never_loaded, - .unloaded_parse_failure, - .unloaded_sema_failure, - .unloaded_success, - => {}, - - .loaded_success => { - self.contents.module.deinit(gpa); - gpa.destroy(self.contents.module); - self.contents = .{ .not_available = {} }; - self.status = .unloaded_success; - }, - .loaded_sema_failure => { - self.contents.module.deinit(gpa); - gpa.destroy(self.contents.module); - self.contents = .{ .not_available = {} }; - self.status = .unloaded_sema_failure; - }, - } - switch (self.source) { - .bytes => |bytes| { - gpa.free(bytes); - self.source = .{ .unloaded = {} }; - }, - .unloaded => {}, - } - } - - pub fn deinit(self: *ZIRModule, gpa: *Allocator) void { - self.decls.deinit(gpa); - self.unload(gpa); - self.* = undefined; - } - - pub fn removeDecl(self: *ZIRModule, child: *Decl) void { - for (self.decls.items) |item, i| { - if (item == child) { - _ = self.decls.swapRemove(i); - return; - } - } - } - - pub fn dumpSrc(self: *ZIRModule, src: usize) void { - const loc = std.zig.findLineColumn(self.source.bytes, src); - std.debug.print("{s}:{d}:{d}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); - } - - pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 { - switch (self.source) { - .unloaded => { - const source = try module.root_pkg.root_src_directory.handle.readFileAllocOptions( - module.gpa, - self.sub_file_path, - std.math.maxInt(u32), - null, - 1, - 0, - ); - self.source = .{ .bytes = source }; - return source; - }, - .bytes => |bytes| return bytes, - } - } - - pub fn fullyQualifiedNameHash(self: *ZIRModule, name: []const u8) NameHash { - // ZIR modules only have 1 file with all decls global in the same namespace. - return std.zig.hashSrc(name); - } - }; - /// This is a temporary structure, references to it are valid only /// during semantic analysis of the block. pub const Block = struct { @@ -794,9 +625,14 @@ pub const Scope = struct { /// Maps ZIR to TZIR. Shared to sub-blocks. inst_table: *InstTable, func: ?*Fn, - decl: *Decl, + /// When analyzing an inline function call, owner_decl is the Decl of the caller + /// and src_decl is the Decl of the callee. + /// This Decl owns the arena memory of this Block. + owner_decl: *Decl, + /// This Decl is the Decl according to the Zig source code corresponding to this Block. + src_decl: *Decl, instructions: ArrayListUnmanaged(*Inst), - /// Points to the arena allocator of DeclAnalysis + /// Points to the arena allocator of the Decl. arena: *Allocator, label: ?Label = null, inlining: ?*Inlining, @@ -845,21 +681,12 @@ pub const Scope = struct { } }; - /// This is a temporary structure, references to it are valid only - /// during semantic analysis of the decl. - pub const DeclAnalysis = struct { - pub const base_tag: Tag = .decl; - base: Scope = Scope{ .tag = base_tag }, - decl: *Decl, - arena: std.heap.ArenaAllocator, - }; - /// This is a temporary structure, references to it are valid only /// during semantic analysis of the decl. pub const GenZIR = struct { pub const base_tag: Tag = .gen_zir; base: Scope = Scope{ .tag = base_tag }, - /// Parents can be: `GenZIR`, `ZIRModule`, `File` + /// Parents can be: `GenZIR`, `File` parent: *Scope, decl: *Decl, arena: *Allocator, @@ -905,11 +732,73 @@ pub const Scope = struct { }; }; +/// This struct holds data necessary to construct API-facing `AllErrors.Message`. +/// Its memory is managed with the general purpose allocator so that they +/// can be created and destroyed in response to incremental updates. +/// In some cases, the Scope.File could have been inferred from where the ErrorMsg +/// is stored. For example, if it is stored in Module.failed_decls, then the Scope.File +/// would be determined by the Decl Scope. However, the data structure contains the field +/// anyway so that `ErrorMsg` can be reused for error notes, which may be in a different +/// file than the parent error message. It also simplifies processing of error messages. +pub const ErrorMsg = struct { + src_loc: SrcLoc, + msg: []const u8, + notes: []ErrorMsg = &.{}, + + pub fn create( + gpa: *Allocator, + src_loc: SrcLoc, + comptime format: []const u8, + args: anytype, + ) !*ErrorMsg { + const self = try gpa.create(ErrorMsg); + errdefer gpa.destroy(self); + self.* = try init(gpa, src_loc, format, args); + return self; + } + + /// Assumes the ErrorMsg struct and msg were both allocated with `gpa`, + /// as well as all notes. + pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void { + self.deinit(gpa); + gpa.destroy(self); + } + + pub fn init( + gpa: *Allocator, + src_loc: SrcLoc, + comptime format: []const u8, + args: anytype, + ) !ErrorMsg { + return ErrorMsg{ + .src_loc = src_loc, + .msg = try std.fmt.allocPrint(gpa, format, args), + }; + } + + pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void { + for (self.notes) |*note| { + note.deinit(gpa); + } + gpa.free(self.notes); + gpa.free(self.msg); + self.* = undefined; + } +}; + +/// Canonical reference to a position within a source file. +pub const SrcLoc = struct { + file_scope: *Scope.File, + byte_offset: usize, +}; + pub const InnerError = error{ OutOfMemory, AnalysisFail }; pub fn deinit(self: *Module) void { const gpa = self.gpa; + self.compile_log_text.deinit(gpa); + self.zig_cache_artifact_directory.handle.close(); self.deletion_set.deinit(gpa); @@ -939,9 +828,6 @@ pub fn deinit(self: *Module) void { } self.failed_exports.deinit(gpa); - for (self.compile_log_decls.items()) |*entry| { - entry.value.deinit(gpa); - } self.compile_log_decls.deinit(gpa); for (self.decl_exports.items()) |entry| { @@ -965,7 +851,7 @@ pub fn deinit(self: *Module) void { self.global_error_set.deinit(gpa); for (self.import_table.items()) |entry| { - entry.value.base.destroy(gpa); + entry.value.destroy(gpa); } self.import_table.deinit(gpa); } @@ -978,7 +864,7 @@ fn freeExportList(gpa: *Allocator, export_list: []*Export) void { gpa.free(export_list); } -pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { +pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void { const tracy = trace(@src()); defer tracy.end(); @@ -999,7 +885,7 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { // The exports this Decl performs will be re-discovered, so we remove them here // prior to re-analysis. - self.deleteDeclExports(decl); + mod.deleteDeclExports(decl); // Dependencies will be re-discovered, so we remove them here prior to re-analysis. for (decl.dependencies.items()) |entry| { const dep = entry.key; @@ -1008,7 +894,7 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { // We don't perform a deletion here, because this Decl or another one // may end up referencing it before the update is complete. dep.deletion_flag = true; - try self.deletion_set.append(self.gpa, dep); + try mod.deletion_set.append(mod.gpa, dep); } } decl.dependencies.clearRetainingCapacity(); @@ -1019,24 +905,21 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { .unreferenced => false, }; - const type_changed = if (self.root_scope.cast(Scope.ZIRModule)) |zir_module| - try zir_sema.analyzeZirDecl(self, decl, zir_module.contents.module.decls[decl.src_index]) - else - self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => return error.AnalysisFail, - else => { - try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); - self.failed_decls.putAssumeCapacityNoClobber(decl, try Compilation.ErrorMsg.create( - self.gpa, - decl.src(), - "unable to analyze: {s}", - .{@errorName(err)}, - )); - decl.analysis = .sema_failure_retryable; - return error.AnalysisFail; - }, - }; + const type_changed = mod.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => return error.AnalysisFail, + else => { + decl.analysis = .sema_failure_retryable; + try mod.failed_decls.ensureCapacity(mod.gpa, mod.failed_decls.items().len + 1); + mod.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + mod.gpa, + decl.srcLoc(), + "unable to analyze: {s}", + .{@errorName(err)}, + )); + return error.AnalysisFail; + }, + }; if (subsequent_analysis) { // We may need to chase the dependants and re-analyze them. @@ -1055,8 +938,8 @@ pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { .codegen_failure, .codegen_failure_retryable, .complete, - => if (dep.generation != self.generation) { - try self.markOutdatedDecl(dep); + => if (dep.generation != mod.generation) { + try mod.markOutdatedDecl(dep); }, } } @@ -1068,8 +951,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const tracy = trace(@src()); defer tracy.end(); - const container_scope = decl.scope.cast(Scope.Container).?; - const tree = try self.getAstTree(container_scope.file_scope); + const tree = try self.getAstTree(decl.container.file_scope); const ast_node = tree.root_node.decls()[decl.src_index]; switch (ast_node.tag) { .FnProto => { @@ -1085,7 +967,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var fn_type_scope: Scope.GenZIR = .{ .decl = decl, .arena = &fn_type_scope_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer fn_type_scope.instructions.deinit(self.gpa); @@ -1197,7 +1079,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .parent = null, .inst_table = &inst_table, .func = null, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &decl_arena.allocator, .inlining = null, @@ -1242,12 +1125,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const new_func = try decl_arena.allocator.create(Fn); const fn_payload = try decl_arena.allocator.create(Value.Payload.Function); - const fn_zir: zir.Module.Body = blk: { + const fn_zir: zir.Body = blk: { // We put the ZIR inside the Decl arena. var gen_scope: Scope.GenZIR = .{ .decl = decl, .arena = &decl_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1400,7 +1283,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .parent = null, .inst_table = &decl_inst_table, .func = null, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &decl_arena.allocator, .inlining = null, @@ -1444,7 +1328,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var gen_scope: Scope.GenZIR = .{ .decl = decl, .arena = &gen_scope_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1472,7 +1356,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .parent = null, .inst_table = &var_inst_table, .func = null, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &gen_scope_arena.allocator, .inlining = null, @@ -1503,7 +1388,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var type_scope: Scope.GenZIR = .{ .decl = decl, .arena = &type_scope_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer type_scope.instructions.deinit(self.gpa); @@ -1584,7 +1469,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var gen_scope: Scope.GenZIR = .{ .decl = decl, .arena = &analysis_arena.allocator, - .parent = decl.scope, + .parent = &decl.container.base, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1602,7 +1487,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .parent = null, .inst_table = &inst_table, .func = null, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &analysis_arena.allocator, .inlining = null, @@ -1632,44 +1518,6 @@ fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void dependee.dependants.putAssumeCapacity(depender, {}); } -fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { - switch (root_scope.status) { - .never_loaded, .unloaded_success => { - try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); - - const source = try root_scope.getSource(self); - - var keep_zir_module = false; - const zir_module = try self.gpa.create(zir.Module); - defer if (!keep_zir_module) self.gpa.destroy(zir_module); - - zir_module.* = try zir.parse(self.gpa, source); - defer if (!keep_zir_module) zir_module.deinit(self.gpa); - - if (zir_module.error_msg) |src_err_msg| { - self.failed_files.putAssumeCapacityNoClobber( - &root_scope.base, - try Compilation.ErrorMsg.create(self.gpa, src_err_msg.byte_offset, "{s}", .{src_err_msg.msg}), - ); - root_scope.status = .unloaded_parse_failure; - return error.AnalysisFail; - } - - root_scope.status = .loaded_success; - root_scope.contents = .{ .module = zir_module }; - keep_zir_module = true; - - return zir_module; - }, - - .unloaded_parse_failure, - .unloaded_sema_failure, - => return error.AnalysisFail, - - .loaded_success, .loaded_sema_failure => return root_scope.contents.module, - } -} - pub fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { const tracy = trace(@src()); defer tracy.end(); @@ -1691,10 +1539,13 @@ pub fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { defer msg.deinit(); try parse_err.render(tree.token_ids, msg.writer()); - const err_msg = try self.gpa.create(Compilation.ErrorMsg); + const err_msg = try self.gpa.create(ErrorMsg); err_msg.* = .{ + .src_loc = .{ + .file_scope = root_scope, + .byte_offset = tree.token_locs[parse_err.loc()].start, + }, .msg = msg.toOwnedSlice(), - .byte_offset = tree.token_locs[parse_err.loc()].start, }; self.failed_files.putAssumeCapacityNoClobber(&root_scope.base, err_msg); @@ -1753,9 +1604,12 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.src_index = decl_i; if (deleted_decls.swapRemove(decl) == null) { decl.analysis = .sema_failure; - const err_msg = try Compilation.ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{s}'", .{decl.name}); - errdefer err_msg.destroy(self.gpa); - try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); + const msg = try ErrorMsg.create(self.gpa, .{ + .file_scope = container_scope.file_scope, + .byte_offset = tree.token_locs[name_tok].start, + }, "redefinition of '{s}'", .{decl.name}); + errdefer msg.destroy(self.gpa); + try self.failed_decls.putNoClobber(self.gpa, decl, msg); } else { if (!srcHashEql(decl.contents_hash, contents_hash)) { try self.markOutdatedDecl(decl); @@ -1795,7 +1649,10 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void decl.src_index = decl_i; if (deleted_decls.swapRemove(decl) == null) { decl.analysis = .sema_failure; - const err_msg = try Compilation.ErrorMsg.create(self.gpa, name_loc.start, "redefinition of '{s}'", .{decl.name}); + const err_msg = try ErrorMsg.create(self.gpa, .{ + .file_scope = container_scope.file_scope, + .byte_offset = name_loc.start, + }, "redefinition of '{s}'", .{decl.name}); errdefer err_msg.destroy(self.gpa); try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); } else if (!srcHashEql(decl.contents_hash, contents_hash)) { @@ -1840,65 +1697,12 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void } } -pub fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { - // We may be analyzing it for the first time, or this may be - // an incremental update. This code handles both cases. - const src_module = try self.getSrcModule(root_scope); - - try self.comp.work_queue.ensureUnusedCapacity(src_module.decls.len); - try root_scope.decls.ensureCapacity(self.gpa, src_module.decls.len); - - var exports_to_resolve = std.ArrayList(*zir.Decl).init(self.gpa); - defer exports_to_resolve.deinit(); - - // Keep track of the decls that we expect to see in this file so that - // we know which ones have been deleted. - var deleted_decls = std.AutoArrayHashMap(*Decl, void).init(self.gpa); - defer deleted_decls.deinit(); - try deleted_decls.ensureCapacity(self.decl_table.items().len); - for (self.decl_table.items()) |entry| { - deleted_decls.putAssumeCapacityNoClobber(entry.value, {}); - } - - for (src_module.decls) |src_decl, decl_i| { - const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name); - if (self.decl_table.get(name_hash)) |decl| { - deleted_decls.removeAssertDiscard(decl); - if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) { - try self.markOutdatedDecl(decl); - decl.contents_hash = src_decl.contents_hash; - } - } else { - const new_decl = try self.createNewDecl( - &root_scope.base, - src_decl.name, - decl_i, - name_hash, - src_decl.contents_hash, - ); - root_scope.decls.appendAssumeCapacity(new_decl); - if (src_decl.inst.cast(zir.Inst.Export)) |export_inst| { - try exports_to_resolve.append(src_decl); - } - } - } - for (exports_to_resolve.items) |export_decl| { - _ = try zir_sema.resolveZirDecl(self, &root_scope.base, export_decl); - } - // Handle explicitly deleted decls from the source code. Not to be confused - // with when we delete decls because they are no longer referenced. - for (deleted_decls.items()) |entry| { - log.debug("noticed '{s}' deleted from source\n", .{entry.key.name}); - try self.deleteDecl(entry.key); - } -} - pub fn deleteDecl(self: *Module, decl: *Decl) !void { try self.deletion_set.ensureCapacity(self.gpa, self.deletion_set.items.len + decl.dependencies.items().len); // Remove from the namespace it resides in. In the case of an anonymous Decl it will // not be present in the set, and this does nothing. - decl.scope.removeDecl(decl); + decl.container.removeDecl(decl); log.debug("deleting decl '{s}'\n", .{decl.name}); const name_hash = decl.fullyQualifiedNameHash(); @@ -1929,9 +1733,7 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { if (self.emit_h_failed_decls.swapRemove(decl)) |entry| { entry.value.destroy(self.gpa); } - if (self.compile_log_decls.swapRemove(decl)) |*entry| { - entry.value.deinit(self.gpa); - } + _ = self.compile_log_decls.swapRemove(decl); self.deleteDeclExports(decl); self.comp.bin_file.freeDecl(decl); @@ -1993,7 +1795,8 @@ pub fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { .parent = null, .inst_table = &inst_table, .func = func, - .decl = decl, + .owner_decl = decl, + .src_decl = decl, .instructions = .{}, .arena = &arena.allocator, .inlining = null, @@ -2022,9 +1825,7 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { if (self.emit_h_failed_decls.swapRemove(decl)) |entry| { entry.value.destroy(self.gpa); } - if (self.compile_log_decls.swapRemove(decl)) |*entry| { - entry.value.deinit(self.gpa); - } + _ = self.compile_log_decls.swapRemove(decl); decl.analysis = .outdated; } @@ -2046,7 +1847,7 @@ fn allocateNewDecl( new_decl.* = .{ .name = "", - .scope = scope.namespace(), + .container = scope.namespace(), .src_index = src_index, .typed_value = .{ .never_succeeded = {} }, .analysis = .unreferenced, @@ -2129,34 +1930,34 @@ pub fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { } pub fn analyzeExport( - self: *Module, + mod: *Module, scope: *Scope, src: usize, borrowed_symbol_name: []const u8, exported_decl: *Decl, ) !void { - try self.ensureDeclAnalyzed(exported_decl); + try mod.ensureDeclAnalyzed(exported_decl); const typed_value = exported_decl.typed_value.most_recent.typed_value; switch (typed_value.ty.zigTypeTag()) { .Fn => {}, - else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), + else => return mod.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), } - try self.decl_exports.ensureCapacity(self.gpa, self.decl_exports.items().len + 1); - try self.export_owners.ensureCapacity(self.gpa, self.export_owners.items().len + 1); + try mod.decl_exports.ensureCapacity(mod.gpa, mod.decl_exports.items().len + 1); + try mod.export_owners.ensureCapacity(mod.gpa, mod.export_owners.items().len + 1); - const new_export = try self.gpa.create(Export); - errdefer self.gpa.destroy(new_export); + const new_export = try mod.gpa.create(Export); + errdefer mod.gpa.destroy(new_export); - const symbol_name = try self.gpa.dupe(u8, borrowed_symbol_name); - errdefer self.gpa.free(symbol_name); + const symbol_name = try mod.gpa.dupe(u8, borrowed_symbol_name); + errdefer mod.gpa.free(symbol_name); - const owner_decl = scope.decl().?; + const owner_decl = scope.ownerDecl().?; new_export.* = .{ .options = .{ .name = symbol_name }, .src = src, - .link = switch (self.comp.bin_file.tag) { + .link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = {} }, .elf => .{ .elf = link.File.Elf.Export{} }, .macho => .{ .macho = link.File.MachO.Export{} }, @@ -2169,48 +1970,53 @@ pub fn analyzeExport( }; // Add to export_owners table. - const eo_gop = self.export_owners.getOrPutAssumeCapacity(owner_decl); + const eo_gop = mod.export_owners.getOrPutAssumeCapacity(owner_decl); if (!eo_gop.found_existing) { eo_gop.entry.value = &[0]*Export{}; } - eo_gop.entry.value = try self.gpa.realloc(eo_gop.entry.value, eo_gop.entry.value.len + 1); + eo_gop.entry.value = try mod.gpa.realloc(eo_gop.entry.value, eo_gop.entry.value.len + 1); eo_gop.entry.value[eo_gop.entry.value.len - 1] = new_export; - errdefer eo_gop.entry.value = self.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); + errdefer eo_gop.entry.value = mod.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); // Add to exported_decl table. - const de_gop = self.decl_exports.getOrPutAssumeCapacity(exported_decl); + const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl); if (!de_gop.found_existing) { de_gop.entry.value = &[0]*Export{}; } - de_gop.entry.value = try self.gpa.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); + de_gop.entry.value = try mod.gpa.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); de_gop.entry.value[de_gop.entry.value.len - 1] = new_export; - errdefer de_gop.entry.value = self.gpa.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); + errdefer de_gop.entry.value = mod.gpa.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); - if (self.symbol_exports.get(symbol_name)) |_| { - try self.failed_exports.ensureCapacity(self.gpa, self.failed_exports.items().len + 1); - self.failed_exports.putAssumeCapacityNoClobber(new_export, try Compilation.ErrorMsg.create( - self.gpa, + if (mod.symbol_exports.get(symbol_name)) |other_export| { + new_export.status = .failed_retryable; + try mod.failed_exports.ensureCapacity(mod.gpa, mod.failed_exports.items().len + 1); + const msg = try mod.errMsg( + scope, src, "exported symbol collision: {s}", .{symbol_name}, - )); - // TODO: add a note + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote( + &other_export.owner_decl.container.base, + other_export.src, + msg, + "other symbol here", + .{}, + ); + mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); new_export.status = .failed; return; } - try self.symbol_exports.putNoClobber(self.gpa, symbol_name, new_export); - self.comp.bin_file.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) { + try mod.symbol_exports.putNoClobber(mod.gpa, symbol_name, new_export); + mod.comp.bin_file.updateDeclExports(mod, exported_decl, de_gop.entry.value) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { - try self.failed_exports.ensureCapacity(self.gpa, self.failed_exports.items().len + 1); - self.failed_exports.putAssumeCapacityNoClobber(new_export, try Compilation.ErrorMsg.create( - self.gpa, - src, - "unable to export: {s}", - .{@errorName(err)}, - )); new_export.status = .failed_retryable; + try mod.failed_exports.ensureCapacity(mod.gpa, mod.failed_exports.items().len + 1); + const msg = try mod.errMsg(scope, src, "unable to export: {s}", .{@errorName(err)}); + mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); }, }; } @@ -2476,7 +2282,7 @@ pub fn createAnonymousDecl( typed_value: TypedValue, ) !*Decl { const name_index = self.getNextAnonNameIndex(); - const scope_decl = scope.decl().?; + const scope_decl = scope.ownerDecl().?; const name = try std.fmt.allocPrint(self.gpa, "{s}__anon_{d}", .{ scope_decl.name, name_index }); defer self.gpa.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); @@ -2512,7 +2318,7 @@ pub fn createContainerDecl( decl_arena: *std.heap.ArenaAllocator, typed_value: TypedValue, ) !*Decl { - const scope_decl = scope.decl().?; + const scope_decl = scope.ownerDecl().?; const name = try self.getAnonTypeName(scope, base_token); defer self.gpa.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); @@ -2558,14 +2364,14 @@ pub fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*De } pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst { - const scope_decl = scope.decl().?; + const scope_decl = scope.ownerDecl().?; try self.declareDeclDependency(scope_decl, decl); self.ensureDeclAnalyzed(decl) catch |err| { if (scope.cast(Scope.Block)) |block| { if (block.func) |func| { func.state = .dependency_failure; } else { - block.decl.analysis = .dependency_failure; + block.owner_decl.analysis = .dependency_failure; } } else { scope_decl.analysis = .dependency_failure; @@ -3217,10 +3023,51 @@ fn coerceArrayPtrToMany(self: *Module, scope: *Scope, dest_type: Type, inst: *In return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToMany runtime instruction", .{}); } -pub fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: anytype) InnerError { - @setCold(true); - const err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args); - return self.failWithOwnedErrorMsg(scope, src, err_msg); +/// We don't return a pointer to the new error note because the pointer +/// becomes invalid when you add another one. +pub fn errNote( + mod: *Module, + scope: *Scope, + src: usize, + parent: *ErrorMsg, + comptime format: []const u8, + args: anytype, +) error{OutOfMemory}!void { + const msg = try std.fmt.allocPrint(mod.gpa, format, args); + errdefer mod.gpa.free(msg); + + parent.notes = try mod.gpa.realloc(parent.notes, parent.notes.len + 1); + parent.notes[parent.notes.len - 1] = .{ + .src_loc = .{ + .file_scope = scope.getFileScope(), + .byte_offset = src, + }, + .msg = msg, + }; +} + +pub fn errMsg( + mod: *Module, + scope: *Scope, + src_byte_offset: usize, + comptime format: []const u8, + args: anytype, +) error{OutOfMemory}!*ErrorMsg { + return ErrorMsg.create(mod.gpa, .{ + .file_scope = scope.getFileScope(), + .byte_offset = src_byte_offset, + }, format, args); +} + +pub fn fail( + mod: *Module, + scope: *Scope, + src_byte_offset: usize, + comptime format: []const u8, + args: anytype, +) InnerError { + const err_msg = try mod.errMsg(scope, src_byte_offset, format, args); + return mod.failWithOwnedErrorMsg(scope, err_msg); } pub fn failTok( @@ -3230,7 +3077,6 @@ pub fn failTok( comptime format: []const u8, args: anytype, ) InnerError { - @setCold(true); const src = scope.tree().token_locs[token_index].start; return self.fail(scope, src, format, args); } @@ -3242,80 +3088,36 @@ pub fn failNode( comptime format: []const u8, args: anytype, ) InnerError { - @setCold(true); const src = scope.tree().token_locs[ast_node.firstToken()].start; return self.fail(scope, src, format, args); } -fn addCompileLog(self: *Module, decl: *Decl, src: usize) error{OutOfMemory}!void { - const entry = try self.compile_log_decls.getOrPutValue(self.gpa, decl, .{}); - try entry.value.append(self.gpa, src); -} - -pub fn failCompileLog( - self: *Module, - scope: *Scope, - src: usize, -) InnerError!void { - switch (scope.tag) { - .decl => { - const decl = scope.cast(Scope.DeclAnalysis).?.decl; - try self.addCompileLog(decl, src); - }, - .block => { - const block = scope.cast(Scope.Block).?; - try self.addCompileLog(block.decl, src); - }, - .gen_zir => { - const gen_zir = scope.cast(Scope.GenZIR).?; - try self.addCompileLog(gen_zir.decl, src); - }, - .local_val => { - const gen_zir = scope.cast(Scope.LocalVal).?.gen_zir; - try self.addCompileLog(gen_zir.decl, src); - }, - .local_ptr => { - const gen_zir = scope.cast(Scope.LocalPtr).?.gen_zir; - try self.addCompileLog(gen_zir.decl, src); - }, - .zir_module, - .file, - .container, - => unreachable, - } -} - -fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Compilation.ErrorMsg) InnerError { +pub fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, err_msg: *ErrorMsg) InnerError { + @setCold(true); { errdefer err_msg.destroy(self.gpa); try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); } switch (scope.tag) { - .decl => { - const decl = scope.cast(Scope.DeclAnalysis).?.decl; - decl.analysis = .sema_failure; - decl.generation = self.generation; - self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); - }, .block => { const block = scope.cast(Scope.Block).?; if (block.inlining) |inlining| { if (inlining.shared.caller) |func| { func.state = .sema_failure; } else { - block.decl.analysis = .sema_failure; - block.decl.generation = self.generation; + block.owner_decl.analysis = .sema_failure; + block.owner_decl.generation = self.generation; } } else { if (block.func) |func| { func.state = .sema_failure; } else { - block.decl.analysis = .sema_failure; - block.decl.generation = self.generation; + block.owner_decl.analysis = .sema_failure; + block.owner_decl.generation = self.generation; } } - self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg); + self.failed_decls.putAssumeCapacityNoClobber(block.owner_decl, err_msg); }, .gen_zir => { const gen_zir = scope.cast(Scope.GenZIR).?; @@ -3335,11 +3137,6 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Com gen_zir.decl.generation = self.generation; self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); }, - .zir_module => { - const zir_module = scope.cast(Scope.ZIRModule).?; - zir_module.status = .loaded_sema_failure; - self.failed_files.putAssumeCapacityNoClobber(scope, err_msg); - }, .file => unreachable, .container => unreachable, } @@ -3671,7 +3468,8 @@ pub fn addSafetyCheck(mod: *Module, parent_block: *Scope.Block, ok: *Inst, panic .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, diff --git a/src/astgen.zig b/src/astgen.zig index 53503a0467..4631e46b5d 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -318,7 +318,7 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as // Make a scope to collect generated instructions in the sub-expression. var block_scope: Scope.GenZIR = .{ .parent = parent_scope, - .decl = parent_scope.decl().?, + .decl = parent_scope.ownerDecl().?, .arena = parent_scope.arena(), .instructions = .{}, }; @@ -474,7 +474,7 @@ fn labeledBlockExpr( var block_scope: Scope.GenZIR = .{ .parent = parent_scope, - .decl = parent_scope.decl().?, + .decl = parent_scope.ownerDecl().?, .arena = gen_zir.arena, .instructions = .{}, .break_result_loc = rl, @@ -899,7 +899,7 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con var gen_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1028,7 +1028,13 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con .ty = Type.initTag(.type), .val = val, }); - return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{})); + if (rl == .ref) { + return addZIRInst(mod, scope, src, zir.Inst.DeclRef, .{ .decl = decl }, .{}); + } else { + return rlWrap(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclVal, .{ + .decl = decl, + }, .{})); + } } fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.ErrorSetDecl) InnerError!*zir.Inst { @@ -1084,7 +1090,7 @@ fn orelseCatchExpr( var block_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1266,7 +1272,7 @@ fn boolBinOp( var block_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1412,7 +1418,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn } var block_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1513,7 +1519,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W var expr_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1649,7 +1655,7 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) var for_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1843,7 +1849,7 @@ fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp { fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst { var block_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1885,7 +1891,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node var item_scope: Scope.GenZIR = .{ .parent = scope, - .decl = scope.decl().?, + .decl = scope.ownerDecl().?, .arena = scope.arena(), .instructions = .{}, }; @@ -1922,8 +1928,15 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node // Check for else/_ prong, those are handled last. if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { if (else_src) |src| { - return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{}); - // TODO notes "previous else prong is here" + const msg = try mod.errMsg( + scope, + case_src, + "multiple else prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous else prong is here", .{}); + return mod.failWithOwnedErrorMsg(scope, msg); } else_src = case_src; special_case = case; @@ -1932,8 +1945,15 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) { if (underscore_src) |src| { - return mod.fail(scope, case_src, "multiple '_' prongs in switch expression", .{}); - // TODO notes "previous '_' prong is here" + const msg = try mod.errMsg( + scope, + case_src, + "multiple '_' prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); + return mod.failWithOwnedErrorMsg(scope, msg); } underscore_src = case_src; special_case = case; @@ -1942,9 +1962,16 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node if (else_src) |some_else| { if (underscore_src) |some_underscore| { - return mod.fail(scope, switch_src, "else and '_' prong in switch expression", .{}); - // TODO notes "else prong is here" - // TODO notes "'_' prong is here" + const msg = try mod.errMsg( + scope, + switch_src, + "else and '_' prong in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, some_else, msg, "else prong is here", .{}); + try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); + return mod.failWithOwnedErrorMsg(scope, msg); } } @@ -2162,7 +2189,13 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo } if (mod.lookupDeclName(scope, ident_name)) |decl| { - return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{})); + if (rl == .ref) { + return addZIRInst(mod, scope, src, zir.Inst.DeclRef, .{ .decl = decl }, .{}); + } else { + return rlWrap(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclVal, .{ + .decl = decl, + }, .{})); + } } return mod.failNode(scope, &ident.base, "use of undeclared identifier '{s}'", .{ident_name}); @@ -2927,6 +2960,8 @@ fn rlWrapVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, resul return rlWrap(mod, scope, rl, void_inst); } +/// TODO go over all the callsites and see where we can introduce "by-value" ZIR instructions +/// to save ZIR memory. For example, see DeclVal vs DeclRef. fn rlWrapPtr(mod: *Module, scope: *Scope, rl: ResultLoc, ptr: *zir.Inst) InnerError!*zir.Inst { if (rl == .ref) return ptr; @@ -3032,7 +3067,7 @@ pub fn addZIRInstBlock( scope: *Scope, src: usize, tag: zir.Inst.Tag, - body: zir.Module.Body, + body: zir.Body, ) !*zir.Inst.Block { const gen_zir = scope.getGenZIR(); try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); @@ -3070,7 +3105,7 @@ pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: Typ } /// TODO The existence of this function is a workaround for a bug in stage1. -pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop { +pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Body) !*zir.Inst.Loop { const P = std.meta.fieldInfo(zir.Inst.Loop, .positionals).field_type; return addZIRInstSpecial(mod, scope, src, zir.Inst.Loop, P{ .body = body }, .{}); } diff --git a/src/codegen.zig b/src/codegen.zig index 709c91a635..9f2fbaab78 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -9,7 +9,7 @@ const TypedValue = @import("TypedValue.zig"); const link = @import("link.zig"); const Module = @import("Module.zig"); const Compilation = @import("Compilation.zig"); -const ErrorMsg = Compilation.ErrorMsg; +const ErrorMsg = Module.ErrorMsg; const Target = std.Target; const Allocator = mem.Allocator; const trace = @import("tracy.zig").trace; @@ -74,7 +74,7 @@ pub const DebugInfoOutput = union(enum) { pub fn generateSymbol( bin_file: *link.File, - src: usize, + src_loc: Module.SrcLoc, typed_value: TypedValue, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, @@ -87,56 +87,56 @@ pub fn generateSymbol( switch (bin_file.options.target.cpu.arch) { .wasm32 => unreachable, // has its own code path .wasm64 => unreachable, // has its own code path - .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, debug_output), - .armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, debug_output), - .aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, debug_output), - .aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, debug_output), - .aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.arc => return Function(.arc).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.avr => return Function(.avr).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.hexagon => return Function(.hexagon).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.mips => return Function(.mips).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.mipsel => return Function(.mipsel).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.mips64 => return Function(.mips64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.mips64el => return Function(.mips64el).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.msp430 => return Function(.msp430).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.powerpc => return Function(.powerpc).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.r600 => return Function(.r600).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src, typed_value, code, debug_output), - .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.sparc => return Function(.sparc).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, debug_output), - .spu_2 => return Function(.spu_2).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.i386 => return Function(.i386).generateSymbol(bin_file, src, typed_value, code, debug_output), - .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.xcore => return Function(.xcore).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.nvptx => return Function(.nvptx).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.le32 => return Function(.le32).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.le64 => return Function(.le64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.amdil => return Function(.amdil).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.hsail => return Function(.hsail).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.spir => return Function(.spir).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.spir64 => return Function(.spir64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.kalimba => return Function(.kalimba).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.shave => return Function(.shave).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.lanai => return Function(.lanai).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src, typed_value, code, debug_output), - //.ve => return Function(.ve).generateSymbol(bin_file, src, typed_value, code, debug_output), + .arm => return Function(.arm).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .armeb => return Function(.armeb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .aarch64 => return Function(.aarch64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.arc => return Function(.arc).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.avr => return Function(.avr).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.bpfel => return Function(.bpfel).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.bpfeb => return Function(.bpfeb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.hexagon => return Function(.hexagon).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.mips => return Function(.mips).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.mipsel => return Function(.mipsel).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.mips64 => return Function(.mips64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.mips64el => return Function(.mips64el).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.msp430 => return Function(.msp430).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.powerpc => return Function(.powerpc).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.powerpc64 => return Function(.powerpc64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.powerpc64le => return Function(.powerpc64le).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.r600 => return Function(.r600).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.amdgcn => return Function(.amdgcn).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.riscv32 => return Function(.riscv32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .riscv64 => return Function(.riscv64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.sparc => return Function(.sparc).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.s390x => return Function(.s390x).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .spu_2 => return Function(.spu_2).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.tce => return Function(.tce).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.tcele => return Function(.tcele).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.thumb => return Function(.thumb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.thumbeb => return Function(.thumbeb).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.i386 => return Function(.i386).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + .x86_64 => return Function(.x86_64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.xcore => return Function(.xcore).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.nvptx => return Function(.nvptx).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.nvptx64 => return Function(.nvptx64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.le32 => return Function(.le32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.le64 => return Function(.le64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.amdil => return Function(.amdil).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.amdil64 => return Function(.amdil64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.hsail => return Function(.hsail).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.hsail64 => return Function(.hsail64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.spir => return Function(.spir).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.spir64 => return Function(.spir64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.kalimba => return Function(.kalimba).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.shave => return Function(.shave).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.lanai => return Function(.lanai).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.renderscript32 => return Function(.renderscript32).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.renderscript64 => return Function(.renderscript64).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), + //.ve => return Function(.ve).generateSymbol(bin_file, src_loc, typed_value, code, debug_output), else => @panic("Backend architectures that don't have good support yet are commented out, to improve compilation performance. If you are interested in one of these other backends feel free to uncomment them. Eventually these will be completed, but stage1 is slow and a memory hog."), } }, @@ -147,7 +147,7 @@ pub fn generateSymbol( try code.ensureCapacity(code.items.len + payload.data.len + 1); code.appendSliceAssumeCapacity(payload.data); const prev_len = code.items.len; - switch (try generateSymbol(bin_file, src, .{ + switch (try generateSymbol(bin_file, src_loc, .{ .ty = typed_value.ty.elemType(), .val = sentinel, }, code, debug_output)) { @@ -165,7 +165,7 @@ pub fn generateSymbol( return Result{ .fail = try ErrorMsg.create( bin_file.allocator, - src, + src_loc, "TODO implement generateSymbol for more kinds of arrays", .{}, ), @@ -200,7 +200,7 @@ pub fn generateSymbol( return Result{ .fail = try ErrorMsg.create( bin_file.allocator, - src, + src_loc, "TODO implement generateSymbol for pointer {}", .{typed_value.val}, ), @@ -217,7 +217,7 @@ pub fn generateSymbol( return Result{ .fail = try ErrorMsg.create( bin_file.allocator, - src, + src_loc, "TODO implement generateSymbol for int type '{}'", .{typed_value.ty}, ), @@ -227,7 +227,7 @@ pub fn generateSymbol( return Result{ .fail = try ErrorMsg.create( bin_file.allocator, - src, + src_loc, "TODO implement generateSymbol for type '{s}'", .{@tagName(t)}, ), @@ -259,7 +259,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { ret_mcv: MCValue, fn_type: Type, arg_index: usize, - src: usize, + src_loc: Module.SrcLoc, stack_align: u32, /// Byte offset within the source file. @@ -428,7 +428,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { fn generateSymbol( bin_file: *link.File, - src: usize, + src_loc: Module.SrcLoc, typed_value: TypedValue, code: *std.ArrayList(u8), debug_output: DebugInfoOutput, @@ -450,19 +450,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try branch_stack.append(.{}); const src_data: struct { lbrace_src: usize, rbrace_src: usize, source: []const u8 } = blk: { - if (module_fn.owner_decl.scope.cast(Module.Scope.Container)) |container_scope| { - const tree = container_scope.file_scope.contents.tree; - const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?; - const block = fn_proto.getBodyNode().?.castTag(.Block).?; - const lbrace_src = tree.token_locs[block.lbrace].start; - const rbrace_src = tree.token_locs[block.rbrace].start; - break :blk .{ .lbrace_src = lbrace_src, .rbrace_src = rbrace_src, .source = tree.source }; - } else if (module_fn.owner_decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { - const byte_off = zir_module.contents.module.decls[module_fn.owner_decl.src_index].inst.src; - break :blk .{ .lbrace_src = byte_off, .rbrace_src = byte_off, .source = zir_module.source.bytes }; - } else { - unreachable; - } + const container_scope = module_fn.owner_decl.container; + const tree = container_scope.file_scope.contents.tree; + const fn_proto = tree.root_node.decls()[module_fn.owner_decl.src_index].castTag(.FnProto).?; + const block = fn_proto.getBodyNode().?.castTag(.Block).?; + const lbrace_src = tree.token_locs[block.lbrace].start; + const rbrace_src = tree.token_locs[block.rbrace].start; + break :blk .{ + .lbrace_src = lbrace_src, + .rbrace_src = rbrace_src, + .source = tree.source, + }; }; var function = Self{ @@ -478,7 +476,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .fn_type = fn_type, .arg_index = 0, .branch_stack = &branch_stack, - .src = src, + .src_loc = src_loc, .stack_align = undefined, .prev_di_pc = 0, .prev_di_src = src_data.lbrace_src, @@ -489,7 +487,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { defer function.stack.deinit(bin_file.allocator); defer function.exitlude_jump_relocs.deinit(bin_file.allocator); - var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) { + var call_info = function.resolveCallingConventionValues(src_loc.byte_offset, fn_type) catch |err| switch (err) { error.CodegenFail => return Result{ .fail = function.err_msg.? }, else => |e| return e, }; @@ -536,12 +534,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const stack_end = self.max_end_stack; if (stack_end > math.maxInt(i32)) - return self.fail(self.src, "too much stack used in call parameters", .{}); + return self.failSymbol("too much stack used in call parameters", .{}); const aligned_stack_end = mem.alignForward(stack_end, self.stack_align); mem.writeIntLittle(u32, self.code.items[reloc_index..][0..4], @intCast(u32, aligned_stack_end)); if (self.code.items.len >= math.maxInt(i32)) { - return self.fail(self.src, "unable to perform relocation: jump too far", .{}); + return self.failSymbol("unable to perform relocation: jump too far", .{}); } if (self.exitlude_jump_relocs.items.len == 1) { self.code.items.len -= 5; @@ -598,7 +596,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (Instruction.Operand.fromU32(@intCast(u32, aligned_stack_end))) |op| { writeInt(u32, self.code.items[backpatch_reloc..][0..4], Instruction.sub(.al, .sp, .sp, op).toU32()); } else { - return self.fail(self.src, "TODO ARM: allow larger stacks", .{}); + return self.failSymbol("TODO ARM: allow larger stacks", .{}); } try self.dbgSetEpilogueBegin(); @@ -624,7 +622,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (math.cast(i26, amt)) |offset| { writeInt(u32, self.code.items[jmp_reloc..][0..4], Instruction.b(.al, offset).toU32()); } else |err| { - return self.fail(self.src, "exitlude jump is too large", .{}); + return self.failSymbol("exitlude jump is too large", .{}); } } } @@ -3678,7 +3676,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) InnerError { @setCold(true); assert(self.err_msg == null); - self.err_msg = try ErrorMsg.create(self.bin_file.allocator, src, format, args); + self.err_msg = try ErrorMsg.create(self.bin_file.allocator, .{ + .file_scope = self.src_loc.file_scope, + .byte_offset = src, + }, format, args); + return error.CodegenFail; + } + + fn failSymbol(self: *Self, comptime format: []const u8, args: anytype) InnerError { + @setCold(true); + assert(self.err_msg == null); + self.err_msg = try ErrorMsg.create(self.bin_file.allocator, self.src_loc, format, args); return error.CodegenFail; } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 8c85f482fd..b26f753757 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -114,10 +114,13 @@ pub const DeclGen = struct { module: *Module, decl: *Decl, fwd_decl: std.ArrayList(u8), - error_msg: ?*Compilation.ErrorMsg, + error_msg: ?*Module.ErrorMsg, fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { - dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args); + dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{ + .file_scope = dg.decl.getFileScope(), + .byte_offset = src, + }, format, args); return error.AnalysisFail; } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5d753c41cb..1edd466d54 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -148,7 +148,7 @@ pub const LLVMIRModule = struct { object_path: []const u8, gpa: *Allocator, - err_msg: ?*Compilation.ErrorMsg = null, + err_msg: ?*Module.ErrorMsg = null, // TODO: The fields below should really move into a different struct, // because they are only valid when generating a function @@ -177,6 +177,8 @@ pub const LLVMIRModule = struct { break_vals: *BreakValues, }) = .{}, + src_loc: Module.SrcLoc, + const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock); const BreakValues = std.ArrayListUnmanaged(*const llvm.Value); @@ -254,6 +256,8 @@ pub const LLVMIRModule = struct { .builder = builder, .object_path = object_path, .gpa = gpa, + // TODO move this field into a struct that is only instantiated per gen() call + .src_loc = undefined, }; return self; } @@ -335,6 +339,8 @@ pub const LLVMIRModule = struct { const typed_value = decl.typed_value.most_recent.typed_value; const src = decl.src(); + self.src_loc = decl.srcLoc(); + log.debug("gen: {s} type: {}, value: {}", .{ decl.name, typed_value.ty, typed_value.val }); if (typed_value.val.castTag(.function)) |func_payload| { @@ -853,7 +859,10 @@ pub const LLVMIRModule = struct { pub fn fail(self: *LLVMIRModule, src: usize, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { @setCold(true); assert(self.err_msg == null); - self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, format, args); + self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ + .file_scope = self.src_loc.file_scope, + .byte_offset = src, + }, format, args); return error.CodegenFail; } }; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index f7cd9b69ce..981d4ec3a3 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -670,7 +670,7 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void { var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); - const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none); + const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none); const code = switch (res) { .externally_managed => |x| x, .appended => code_buffer.items, @@ -732,7 +732,7 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), ); continue; } @@ -743,7 +743,7 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl, try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: Exports other than '_start'", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than '_start'", .{}), ); continue; } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 8c76a4e967..ee50eb5d94 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2189,22 +2189,14 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { try dbg_line_buffer.ensureCapacity(26); const line_off: u28 = blk: { - if (decl.scope.cast(Module.Scope.Container)) |container_scope| { - const tree = container_scope.file_scope.contents.tree; - const file_ast_decls = tree.root_node.decls(); - // TODO Look into improving the performance here by adding a token-index-to-line - // lookup table. Currently this involves scanning over the source code for newlines. - const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; - const block = fn_proto.getBodyNode().?.castTag(.Block).?; - const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); - break :blk @intCast(u28, line_delta); - } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { - const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src; - const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off); - break :blk @intCast(u28, line_delta); - } else { - unreachable; - } + const tree = decl.container.file_scope.contents.tree; + const file_ast_decls = tree.root_node.decls(); + // TODO Look into improving the performance here by adding a token-index-to-line + // lookup table. Currently this involves scanning over the source code for newlines. + const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; + const block = fn_proto.getBodyNode().?.castTag(.Block).?; + const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); + break :blk @intCast(u28, line_delta); }; const ptr_width_bytes = self.ptrWidthBytes(); @@ -2268,7 +2260,7 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { } else { // TODO implement .debug_info for global variables } - const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{ + const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{ .dwarf = .{ .dbg_line = &dbg_line_buffer, .dbg_info = &dbg_info_buffer, @@ -2642,7 +2634,7 @@ pub fn updateDeclExports( try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), ); continue; } @@ -2660,7 +2652,7 @@ pub fn updateDeclExports( try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: GlobalLinkage.LinkOnce", .{}), ); continue; }, @@ -2703,8 +2695,7 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec if (self.llvm_ir_module) |_| return; - const container_scope = decl.scope.cast(Module.Scope.Container).?; - const tree = container_scope.file_scope.contents.tree; + const tree = decl.container.file_scope.contents.tree; const file_ast_decls = tree.root_node.decls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. diff --git a/src/link/MachO.zig b/src/link/MachO.zig index d913a82328..1017405255 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1148,7 +1148,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { } const res = if (debug_buffers) |*dbg| - try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{ + try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{ .dwarf = .{ .dbg_line = &dbg.dbg_line_buffer, .dbg_info = &dbg.dbg_info_buffer, @@ -1156,7 +1156,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { }, }) else - try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none); + try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none); const code = switch (res) { .externally_managed => |x| x, @@ -1316,7 +1316,7 @@ pub fn updateDeclExports( try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), ); continue; } @@ -1334,7 +1334,7 @@ pub fn updateDeclExports( try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, - try Compilation.ErrorMsg.create(self.base.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), + try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: GlobalLinkage.LinkOnce", .{}), ); continue; }, diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index 81a016ce42..fb7488a12c 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -906,8 +906,7 @@ pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const M const tracy = trace(@src()); defer tracy.end(); - const container_scope = decl.scope.cast(Module.Scope.Container).?; - const tree = container_scope.file_scope.contents.tree; + const tree = decl.container.file_scope.contents.tree; const file_ast_decls = tree.root_node.decls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. @@ -951,22 +950,14 @@ pub fn initDeclDebugBuffers( try dbg_line_buffer.ensureCapacity(26); const line_off: u28 = blk: { - if (decl.scope.cast(Module.Scope.Container)) |container_scope| { - const tree = container_scope.file_scope.contents.tree; - const file_ast_decls = tree.root_node.decls(); - // TODO Look into improving the performance here by adding a token-index-to-line - // lookup table. Currently this involves scanning over the source code for newlines. - const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; - const block = fn_proto.getBodyNode().?.castTag(.Block).?; - const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); - break :blk @intCast(u28, line_delta); - } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| { - const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src; - const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off); - break :blk @intCast(u28, line_delta); - } else { - unreachable; - } + const tree = decl.container.file_scope.contents.tree; + const file_ast_decls = tree.root_node.decls(); + // TODO Look into improving the performance here by adding a token-index-to-line + // lookup table. Currently this involves scanning over the source code for newlines. + const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; + const block = fn_proto.getBodyNode().?.castTag(.Block).?; + const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); + break :blk @intCast(u28, line_delta); }; dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{ diff --git a/src/main.zig b/src/main.zig index 867aa348b1..13bea13a5e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -221,7 +221,6 @@ const usage_build_generic = \\ \\Supported file types: \\ .zig Zig source code - \\ .zir Zig Intermediate Representation code \\ .o ELF object file \\ .o MACH-O (macOS) object file \\ .obj COFF (Windows) object file @@ -245,8 +244,6 @@ const usage_build_generic = \\ -fno-emit-bin Do not output machine code \\ -femit-asm[=path] Output .s (assembly code) \\ -fno-emit-asm (default) Do not output .s (assembly code) - \\ -femit-zir[=path] Produce a .zir file with Zig IR - \\ -fno-emit-zir (default) Do not produce a .zir file with Zig IR \\ -femit-llvm-ir[=path] Produce a .ll file with LLVM IR (requires LLVM extensions) \\ -fno-emit-llvm-ir (default) Do not produce a .ll file with LLVM IR \\ -femit-h[=path] Generate a C header file (.h) @@ -1631,18 +1628,12 @@ fn buildOutputType( var emit_docs_resolved = try emit_docs.resolve("docs"); defer emit_docs_resolved.deinit(); - const zir_out_path: ?[]const u8 = switch (emit_zir) { - .no => null, - .yes_default_path => blk: { - if (root_src_file) |rsf| { - if (mem.endsWith(u8, rsf, ".zir")) { - break :blk try std.fmt.allocPrint(arena, "{s}.out.zir", .{root_name}); - } - } - break :blk try std.fmt.allocPrint(arena, "{s}.zir", .{root_name}); + switch (emit_zir) { + .no => {}, + .yes_default_path, .yes => { + fatal("The -femit-zir implementation has been intentionally deleted so that it can be rewritten as a proper backend.", .{}); }, - .yes => |p| p, - }; + } const root_pkg: ?*Package = if (root_src_file) |src_path| blk: { if (main_pkg_path) |p| { @@ -1753,7 +1744,7 @@ fn buildOutputType( .dll_export_fns = dll_export_fns, .object_format = object_format, .optimize_mode = optimize_mode, - .keep_source_files_loaded = zir_out_path != null, + .keep_source_files_loaded = false, .clang_argv = clang_argv.items, .lld_argv = lld_argv.items, .lib_dirs = lib_dirs.items, @@ -1845,7 +1836,7 @@ fn buildOutputType( } }; - updateModule(gpa, comp, zir_out_path, hook) catch |err| switch (err) { + updateModule(gpa, comp, hook) catch |err| switch (err) { error.SemanticAnalyzeFail => if (!watch) process.exit(1), else => |e| return e, }; @@ -1980,7 +1971,7 @@ fn buildOutputType( if (output_mode == .Exe) { try comp.makeBinFileWritable(); } - updateModule(gpa, comp, zir_out_path, hook) catch |err| switch (err) { + updateModule(gpa, comp, hook) catch |err| switch (err) { error.SemanticAnalyzeFail => continue, else => |e| return e, }; @@ -2003,7 +1994,7 @@ const AfterUpdateHook = union(enum) { update: []const u8, }; -fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8, hook: AfterUpdateHook) !void { +fn updateModule(gpa: *Allocator, comp: *Compilation, hook: AfterUpdateHook) !void { try comp.update(); var errors = try comp.getAllErrorsAlloc(); @@ -2013,6 +2004,10 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8, for (errors.list) |full_err_msg| { full_err_msg.renderToStdErr(); } + const log_text = comp.getCompileLogOutput(); + if (log_text.len != 0) { + std.debug.print("\nCompile Log Output:\n{s}", .{log_text}); + } return error.SemanticAnalyzeFail; } else switch (hook) { .none => {}, @@ -2024,20 +2019,6 @@ fn updateModule(gpa: *Allocator, comp: *Compilation, zir_out_path: ?[]const u8, .{}, ), } - - if (zir_out_path) |zop| { - const module = comp.bin_file.options.module orelse - fatal("-femit-zir with no zig source code", .{}); - var new_zir_module = try zir.emit(gpa, module); - defer new_zir_module.deinit(gpa); - - const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{}); - defer baf.destroy(); - - try new_zir_module.writeToStream(gpa, baf.writer()); - - try baf.finish(); - } } fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !void { @@ -2506,7 +2487,7 @@ pub fn cmdBuild(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v }; defer comp.destroy(); - try updateModule(gpa, comp, null, .none); + try updateModule(gpa, comp, .none); try comp.makeBinFileExecutable(); child_argv.items[argv_index_exe] = try comp.bin_file.options.emit.?.directory.join( diff --git a/src/test.zig b/src/test.zig index 59927525df..1c9fb57f01 100644 --- a/src/test.zig +++ b/src/test.zig @@ -15,6 +15,8 @@ const CrossTarget = std.zig.CrossTarget; const zig_h = link.File.C.zig_h; +const hr = "=" ** 40; + test "self-hosted" { var ctx = TestContext.init(); defer ctx.deinit(); @@ -29,23 +31,32 @@ const ErrorMsg = union(enum) { msg: []const u8, line: u32, column: u32, + kind: Kind, }, plain: struct { msg: []const u8, + kind: Kind, }, - fn init(other: Compilation.AllErrors.Message) ErrorMsg { + const Kind = enum { + @"error", + note, + }; + + fn init(other: Compilation.AllErrors.Message, kind: Kind) ErrorMsg { switch (other) { .src => |src| return .{ .src = .{ .msg = src.msg, .line = @intCast(u32, src.line), .column = @intCast(u32, src.column), + .kind = kind, }, }, .plain => |plain| return .{ .plain = .{ .msg = plain.msg, + .kind = kind, }, }, } @@ -59,14 +70,15 @@ const ErrorMsg = union(enum) { ) !void { switch (self) { .src => |src| { - return writer.print(":{d}:{d}: error: {s}", .{ + return writer.print(":{d}:{d}: {s}: {s}", .{ src.line + 1, src.column + 1, + @tagName(src.kind), src.msg, }); }, .plain => |plain| { - return writer.print("error: {s}", .{plain.msg}); + return writer.print("{s}: {s}", .{ plain.msg, @tagName(plain.kind) }); }, } } @@ -86,9 +98,6 @@ pub const TestContext = struct { /// effects of the incremental compilation. src: [:0]const u8, case: union(enum) { - /// A transformation update transforms the input and tests against - /// the expected output ZIR. - Transformation: [:0]const u8, /// Check the main binary output file against an expected set of bytes. /// This is most useful with, for example, `-ofmt=c`. CompareObjectFile: []const u8, @@ -139,15 +148,6 @@ pub const TestContext = struct { files: std.ArrayList(File), - /// Adds a subcase in which the module is updated with `src`, and the - /// resulting ZIR is validated against `result`. - pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void { - self.updates.append(.{ - .src = src, - .case = .{ .Transformation = result }, - }) catch unreachable; - } - /// Adds a subcase in which the module is updated with `src`, and a C /// header is generated. pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void { @@ -182,31 +182,37 @@ pub const TestContext = struct { /// the form `:line:column: error: message`. pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; - for (errors) |e, i| { - if (e[0] != ':') { - array[i] = .{ .plain = .{ .msg = e } }; + for (errors) |err_msg_line, i| { + if (std.mem.startsWith(u8, err_msg_line, "error: ")) { + array[i] = .{ + .plain = .{ .msg = err_msg_line["error: ".len..], .kind = .@"error" }, + }; + continue; + } else if (std.mem.startsWith(u8, err_msg_line, "note: ")) { + array[i] = .{ + .plain = .{ .msg = err_msg_line["note: ".len..], .kind = .note }, + }; continue; } - var cur = e[1..]; - var line_index = std.mem.indexOf(u8, cur, ":"); - if (line_index == null) { - @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); - } - const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); - cur = cur[line_index.? + 1 ..]; - const column_index = std.mem.indexOf(u8, cur, ":"); - if (column_index == null) { - @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); - } - const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); - cur = cur[column_index.? + 2 ..]; - if (!std.mem.eql(u8, cur[0..7], "error: ")) { - @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); - } - const msg = cur[7..]; + // example: ":1:2: error: bad thing happened" + var it = std.mem.split(err_msg_line, ":"); + _ = it.next() orelse @panic("missing colon"); + const line_text = it.next() orelse @panic("missing line"); + const col_text = it.next() orelse @panic("missing column"); + const kind_text = it.next() orelse @panic("missing 'error'/'note'"); + const msg = it.rest()[1..]; // skip over the space at end of "error: " + + const line = std.fmt.parseInt(u32, line_text, 10) catch @panic("bad line number"); + const column = std.fmt.parseInt(u32, col_text, 10) catch @panic("bad column number"); + const kind: ErrorMsg.Kind = if (std.mem.eql(u8, kind_text, " error")) + .@"error" + else if (std.mem.eql(u8, kind_text, " note")) + .note + else + @panic("expected 'error'/'note'"); if (line == 0 or column == 0) { - @panic("Invalid test: error line and column must be specified starting at one!"); + @panic("line and column must be specified starting at one"); } array[i] = .{ @@ -214,6 +220,7 @@ pub const TestContext = struct { .msg = msg, .line = line - 1, .column = column - 1, + .kind = kind, }, }; } @@ -689,25 +696,20 @@ pub const TestContext = struct { var all_errors = try comp.getAllErrorsAlloc(); defer all_errors.deinit(allocator); if (all_errors.list.len != 0) { - std.debug.print("\nErrors occurred updating the compilation:\n================\n", .{}); + std.debug.print("\nErrors occurred updating the compilation:\n{s}\n", .{hr}); for (all_errors.list) |err_msg| { switch (err_msg) { .src => |src| { - std.debug.print(":{d}:{d}: error: {s}\n================\n", .{ - src.line + 1, src.column + 1, src.msg, + std.debug.print(":{d}:{d}: error: {s}\n{s}\n", .{ + src.line + 1, src.column + 1, src.msg, hr, }); }, .plain => |plain| { - std.debug.print("error: {s}\n================\n", .{plain.msg}); + std.debug.print("error: {s}\n{s}\n", .{ plain.msg, hr }); }, } } // TODO print generated C code - //if (comp.bin_file.cast(link.File.C)) |c_file| { - // std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ - // c_file.main.items, - // }); - //} std.debug.print("Test failed.\n", .{}); std.process.exit(1); } @@ -728,48 +730,74 @@ pub const TestContext = struct { std.testing.expectEqualStrings(expected_output, out); }, - .Transformation => |expected_output| { - update_node.setEstimatedTotalItems(5); - var emit_node = update_node.start("emit", 0); - emit_node.activate(); - var new_zir_module = try zir.emit(allocator, comp.bin_file.options.module.?); - defer new_zir_module.deinit(allocator); - emit_node.end(); - - var write_node = update_node.start("write", 0); - write_node.activate(); - var out_zir = std.ArrayList(u8).init(allocator); - defer out_zir.deinit(); - try new_zir_module.writeToStream(allocator, out_zir.writer()); - write_node.end(); - + .Error => |case_error_list| { var test_node = update_node.start("assert", 0); test_node.activate(); defer test_node.end(); - std.testing.expectEqualStrings(expected_output, out_zir.items); - }, - .Error => |e| { - var test_node = update_node.start("assert", 0); - test_node.activate(); - defer test_node.end(); - var handled_errors = try arena.alloc(bool, e.len); - for (handled_errors) |*handled| { - handled.* = false; + const handled_errors = try arena.alloc(bool, case_error_list.len); + std.mem.set(bool, handled_errors, false); + + var actual_errors = try comp.getAllErrorsAlloc(); + defer actual_errors.deinit(allocator); + + var any_failed = false; + var notes_to_check = std.ArrayList(*const Compilation.AllErrors.Message).init(allocator); + defer notes_to_check.deinit(); + + for (actual_errors.list) |actual_error| { + for (case_error_list) |case_msg, i| { + const ex_tag: @TagType(@TypeOf(case_msg)) = case_msg; + switch (actual_error) { + .src => |actual_msg| { + for (actual_msg.notes) |*note| { + try notes_to_check.append(note); + } + + if (ex_tag != .src) continue; + + if (actual_msg.line == case_msg.src.line and + actual_msg.column == case_msg.src.column and + std.mem.eql(u8, case_msg.src.msg, actual_msg.msg) and + case_msg.src.kind == .@"error") + { + handled_errors[i] = true; + break; + } + }, + .plain => |plain| { + if (ex_tag != .plain) continue; + + if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and + case_msg.plain.kind == .@"error") + { + handled_errors[i] = true; + break; + } + }, + } + } else { + std.debug.print( + "\nUnexpected error:\n{s}\n{}\n{s}", + .{ hr, ErrorMsg.init(actual_error, .@"error"), hr }, + ); + any_failed = true; + } } - var all_errors = try comp.getAllErrorsAlloc(); - defer all_errors.deinit(allocator); - for (all_errors.list) |a| { - for (e) |ex, i| { - const a_tag: @TagType(@TypeOf(a)) = a; - const ex_tag: @TagType(@TypeOf(ex)) = ex; - switch (a) { - .src => |src| { + while (notes_to_check.popOrNull()) |note| { + for (case_error_list) |case_msg, i| { + const ex_tag: @TagType(@TypeOf(case_msg)) = case_msg; + switch (note.*) { + .src => |actual_msg| { + for (actual_msg.notes) |*sub_note| { + try notes_to_check.append(sub_note); + } if (ex_tag != .src) continue; - if (src.line == ex.src.line and - src.column == ex.src.column and - std.mem.eql(u8, ex.src.msg, src.msg)) + if (actual_msg.line == case_msg.src.line and + actual_msg.column == case_msg.src.column and + std.mem.eql(u8, case_msg.src.msg, actual_msg.msg) and + case_msg.src.kind == .note) { handled_errors[i] = true; break; @@ -778,7 +806,9 @@ pub const TestContext = struct { .plain => |plain| { if (ex_tag != .plain) continue; - if (std.mem.eql(u8, ex.plain.msg, plain.msg)) { + if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and + case_msg.plain.kind == .note) + { handled_errors[i] = true; break; } @@ -786,23 +816,29 @@ pub const TestContext = struct { } } else { std.debug.print( - "{s}\nUnexpected error:\n================\n{}\n================\nTest failed.\n", - .{ case.name, ErrorMsg.init(a) }, + "\nUnexpected note:\n{s}\n{}\n{s}", + .{ hr, ErrorMsg.init(note.*, .note), hr }, ); - std.process.exit(1); + any_failed = true; } } for (handled_errors) |handled, i| { if (!handled) { - const er = e[i]; std.debug.print( - "{s}\nDid not receive error:\n================\n{}\n================\nTest failed.\n", - .{ case.name, er }, + "\nExpected error not found:\n{s}\n{}\n{s}", + .{ hr, case_error_list[i], hr }, ); - std.process.exit(1); + any_failed = true; } } + + if (any_failed) { + std.debug.print("\nTest case '{s}' failed, update_index={d}.\n", .{ + case.name, update_index, + }); + std.process.exit(1); + } }, .Execution => |expected_stdout| { update_node.setEstimatedTotalItems(4); diff --git a/src/type/Enum.zig b/src/type/Enum.zig index 9b9ec5b319..4dfd5f6e44 100644 --- a/src/type/Enum.zig +++ b/src/type/Enum.zig @@ -21,7 +21,7 @@ pub const Field = struct { }; pub const Zir = struct { - body: zir.Module.Body, + body: zir.Body, inst: *zir.Inst, }; diff --git a/src/type/Struct.zig b/src/type/Struct.zig index d6a591c95e..24e3a0dcad 100644 --- a/src/type/Struct.zig +++ b/src/type/Struct.zig @@ -24,7 +24,7 @@ pub const Field = struct { }; pub const Zir = struct { - body: zir.Module.Body, + body: zir.Body, inst: *zir.Inst, }; diff --git a/src/type/Union.zig b/src/type/Union.zig index 26cc1796c6..5c7acf7d36 100644 --- a/src/type/Union.zig +++ b/src/type/Union.zig @@ -24,7 +24,7 @@ pub const Field = struct { }; pub const Zir = struct { - body: zir.Module.Body, + body: zir.Body, inst: *zir.Inst, }; diff --git a/src/zir.zig b/src/zir.zig index 0ccb09efac..0e7b3a3520 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -12,17 +12,6 @@ const TypedValue = @import("TypedValue.zig"); const ir = @import("ir.zig"); const IrModule = @import("Module.zig"); -/// This struct is relevent only for the ZIR Module text format. It is not used for -/// semantic analysis of Zig source code. -pub const Decl = struct { - name: []const u8, - - /// Hash of slice into the source of the part after the = and before the next instruction. - contents_hash: std.zig.SrcHash, - - inst: *Inst, -}; - /// These are instructions that correspond to the ZIR text format. See `ir.Inst` for /// in-memory, analyzed instructions with types and values. /// We use a table to map these instruction to their respective semantically analyzed @@ -141,15 +130,12 @@ pub const Inst = struct { container_field, /// Declares the beginning of a statement. Used for debug info. dbg_stmt, - /// Represents a pointer to a global decl by name. + /// Represents a pointer to a global decl. declref, /// Represents a pointer to a global decl by string name. declref_str, - /// The syntax `@foo` is equivalent to `declval("foo")`. - /// declval is equivalent to declref followed by deref. + /// Equivalent to a declref followed by deref. declval, - /// Same as declval but the parameter is a `*Module.Decl` rather than a name. - declval_in_module, /// Load the value from a pointer. deref, /// Arithmetic division. Asserts no integer overflow. @@ -419,7 +405,6 @@ pub const Inst = struct { .declref => DeclRef, .declref_str => DeclRefStr, .declval => DeclVal, - .declval_in_module => DeclValInModule, .coerce_result_block_ptr => CoerceResultBlockPtr, .compilelog => CompileLog, .loop => Loop, @@ -496,7 +481,6 @@ pub const Inst = struct { .declref, .declref_str, .declval, - .declval_in_module, .deref, .div, .elemptr, @@ -650,7 +634,7 @@ pub const Inst = struct { base: Inst, positionals: struct { - body: Module.Body, + body: Body, }, kw_args: struct {}, }; @@ -705,7 +689,7 @@ pub const Inst = struct { base: Inst, positionals: struct { - name: []const u8, + decl: *IrModule.Decl, }, kw_args: struct {}, }; @@ -724,16 +708,6 @@ pub const Inst = struct { pub const base_tag = Tag.declval; base: Inst, - positionals: struct { - name: []const u8, - }, - kw_args: struct {}, - }; - - pub const DeclValInModule = struct { - pub const base_tag = Tag.declval_in_module; - base: Inst, - positionals: struct { decl: *IrModule.Decl, }, @@ -758,10 +732,7 @@ pub const Inst = struct { positionals: struct { to_log: []*Inst, }, - kw_args: struct { - /// If we have seen it already so don't make another error - seen: bool = false, - }, + kw_args: struct {}, }; pub const Const = struct { @@ -799,7 +770,7 @@ pub const Inst = struct { base: Inst, positionals: struct { - body: Module.Body, + body: Body, }, kw_args: struct {}, }; @@ -838,7 +809,7 @@ pub const Inst = struct { positionals: struct { fn_type: *Inst, - body: Module.Body, + body: Body, }, kw_args: struct { is_inline: bool = false, @@ -998,8 +969,8 @@ pub const Inst = struct { positionals: struct { condition: *Inst, - then_body: Module.Body, - else_body: Module.Body, + then_body: Body, + else_body: Body, }, kw_args: struct {}, }; @@ -1078,7 +1049,7 @@ pub const Inst = struct { /// List of all individual items and ranges items: []*Inst, cases: []Case, - else_body: Module.Body, + else_body: Body, }, kw_args: struct { /// Pointer to first range if such exists. @@ -1092,7 +1063,7 @@ pub const Inst = struct { pub const Case = struct { item: *Inst, - body: Module.Body, + body: Body, }; }; pub const TypeOfPeer = struct { @@ -1192,6 +1163,10 @@ pub const ErrorMsg = struct { msg: []const u8, }; +pub const Body = struct { + instructions: []*Inst, +}; + pub const Module = struct { decls: []*Decl, arena: std.heap.ArenaAllocator, @@ -1199,6 +1174,15 @@ pub const Module = struct { metadata: std.AutoHashMap(*Inst, MetaData), body_metadata: std.AutoHashMap(*Body, BodyMetaData), + pub const Decl = struct { + name: []const u8, + + /// Hash of slice into the source of the part after the = and before the next instruction. + contents_hash: std.zig.SrcHash, + + inst: *Inst, + }; + pub const MetaData = struct { deaths: ir.Inst.DeathsInt, addr: usize, @@ -1208,10 +1192,6 @@ pub const Module = struct { deaths: []*Inst, }; - pub const Body = struct { - instructions: []*Inst, - }; - pub fn deinit(self: *Module, allocator: *Allocator) void { self.metadata.deinit(); self.body_metadata.deinit(); @@ -1369,7 +1349,7 @@ const Writer = struct { } try stream.writeByte(']'); }, - Module.Body => { + Body => { try stream.writeAll("{\n"); if (self.module.body_metadata.get(param_ptr)) |metadata| { if (metadata.deaths.len > 0) { @@ -1468,8 +1448,6 @@ const Writer = struct { try stream.print("@{s}", .{info.name}); } } else if (inst.cast(Inst.DeclVal)) |decl_val| { - try stream.print("@{s}", .{decl_val.positionals.name}); - } else if (inst.cast(Inst.DeclValInModule)) |decl_val| { try stream.print("@{s}", .{decl_val.positionals.decl.name}); } else { // This should be unreachable in theory, but since ZIR is used for debugging the compiler @@ -1479,502 +1457,6 @@ const Writer = struct { } }; -pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module { - var global_name_map = std.StringHashMap(*Inst).init(allocator); - defer global_name_map.deinit(); - - var parser: Parser = .{ - .allocator = allocator, - .arena = std.heap.ArenaAllocator.init(allocator), - .i = 0, - .source = source, - .global_name_map = &global_name_map, - .decls = .{}, - .unnamed_index = 0, - .block_table = std.StringHashMap(*Inst.Block).init(allocator), - .loop_table = std.StringHashMap(*Inst.Loop).init(allocator), - }; - defer parser.block_table.deinit(); - defer parser.loop_table.deinit(); - errdefer parser.arena.deinit(); - - parser.parseRoot() catch |err| switch (err) { - error.ParseFailure => { - assert(parser.error_msg != null); - }, - else => |e| return e, - }; - - return Module{ - .decls = parser.decls.toOwnedSlice(allocator), - .arena = parser.arena, - .error_msg = parser.error_msg, - .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), - .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), - }; -} - -const Parser = struct { - allocator: *Allocator, - arena: std.heap.ArenaAllocator, - i: usize, - source: [:0]const u8, - decls: std.ArrayListUnmanaged(*Decl), - global_name_map: *std.StringHashMap(*Inst), - error_msg: ?ErrorMsg = null, - unnamed_index: usize, - block_table: std.StringHashMap(*Inst.Block), - loop_table: std.StringHashMap(*Inst.Loop), - - const Body = struct { - instructions: std.ArrayList(*Inst), - name_map: *std.StringHashMap(*Inst), - }; - - fn parseBody(self: *Parser, body_ctx: ?*Body) !Module.Body { - var name_map = std.StringHashMap(*Inst).init(self.allocator); - defer name_map.deinit(); - - var body_context = Body{ - .instructions = std.ArrayList(*Inst).init(self.allocator), - .name_map = if (body_ctx) |bctx| bctx.name_map else &name_map, - }; - defer body_context.instructions.deinit(); - - try requireEatBytes(self, "{"); - skipSpace(self); - - while (true) : (self.i += 1) switch (self.source[self.i]) { - ';' => _ = try skipToAndOver(self, '\n'), - '%' => { - self.i += 1; - const ident = try skipToAndOver(self, ' '); - skipSpace(self); - try requireEatBytes(self, "="); - skipSpace(self); - const decl = try parseInstruction(self, &body_context, ident); - const ident_index = body_context.instructions.items.len; - if (try body_context.name_map.fetchPut(ident, decl.inst)) |_| { - return self.fail("redefinition of identifier '{s}'", .{ident}); - } - try body_context.instructions.append(decl.inst); - continue; - }, - ' ', '\n' => continue, - '}' => { - self.i += 1; - break; - }, - else => |byte| return self.failByte(byte), - }; - - // Move the instructions to the arena - const instrs = try self.arena.allocator.alloc(*Inst, body_context.instructions.items.len); - mem.copy(*Inst, instrs, body_context.instructions.items); - return Module.Body{ .instructions = instrs }; - } - - fn parseStringLiteral(self: *Parser) ![]u8 { - const start = self.i; - try self.requireEatBytes("\""); - - while (true) : (self.i += 1) switch (self.source[self.i]) { - '"' => { - self.i += 1; - const span = self.source[start..self.i]; - var bad_index: usize = undefined; - const parsed = std.zig.parseStringLiteral(&self.arena.allocator, span, &bad_index) catch |err| switch (err) { - error.InvalidCharacter => { - self.i = start + bad_index; - const bad_byte = self.source[self.i]; - return self.fail("invalid string literal character: '{c}'\n", .{bad_byte}); - }, - else => |e| return e, - }; - return parsed; - }, - '\\' => { - self.i += 1; - continue; - }, - 0 => return self.failByte(0), - else => continue, - }; - } - - fn parseIntegerLiteral(self: *Parser) !BigIntConst { - const start = self.i; - if (self.source[self.i] == '-') self.i += 1; - while (true) : (self.i += 1) switch (self.source[self.i]) { - '0'...'9' => continue, - else => break, - }; - const number_text = self.source[start..self.i]; - const base = 10; - // TODO reuse the same array list for this - const limbs_buffer_len = std.math.big.int.calcSetStringLimbsBufferLen(base, number_text.len); - const limbs_buffer = try self.allocator.alloc(std.math.big.Limb, limbs_buffer_len); - defer self.allocator.free(limbs_buffer); - const limb_len = std.math.big.int.calcSetStringLimbCount(base, number_text.len); - const limbs = try self.arena.allocator.alloc(std.math.big.Limb, limb_len); - var result = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - result.setString(base, number_text, limbs_buffer, self.allocator) catch |err| switch (err) { - error.InvalidCharacter => { - self.i = start; - return self.fail("invalid digit in integer literal", .{}); - }, - }; - return result.toConst(); - } - - fn parseRoot(self: *Parser) !void { - // The IR format is designed so that it can be tokenized and parsed at the same time. - while (true) { - switch (self.source[self.i]) { - ';' => _ = try skipToAndOver(self, '\n'), - '@' => { - self.i += 1; - const ident = try skipToAndOver(self, ' '); - skipSpace(self); - try requireEatBytes(self, "="); - skipSpace(self); - const decl = try parseInstruction(self, null, ident); - const ident_index = self.decls.items.len; - if (try self.global_name_map.fetchPut(ident, decl.inst)) |_| { - return self.fail("redefinition of identifier '{s}'", .{ident}); - } - try self.decls.append(self.allocator, decl); - }, - ' ', '\n' => self.i += 1, - 0 => break, - else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), - } - } - } - - fn eatByte(self: *Parser, byte: u8) bool { - if (self.source[self.i] != byte) return false; - self.i += 1; - return true; - } - - fn skipSpace(self: *Parser) void { - while (self.source[self.i] == ' ' or self.source[self.i] == '\n') { - self.i += 1; - } - } - - fn requireEatBytes(self: *Parser, bytes: []const u8) !void { - const start = self.i; - for (bytes) |byte| { - if (self.source[self.i] != byte) { - self.i = start; - return self.fail("expected '{s}'", .{bytes}); - } - self.i += 1; - } - } - - fn skipToAndOver(self: *Parser, byte: u8) ![]const u8 { - const start_i = self.i; - while (self.source[self.i] != 0) : (self.i += 1) { - if (self.source[self.i] == byte) { - const result = self.source[start_i..self.i]; - self.i += 1; - return result; - } - } - return self.fail("unexpected EOF", .{}); - } - - /// ParseFailure is an internal error code; handled in `parse`. - const InnerError = error{ ParseFailure, OutOfMemory }; - - fn failByte(self: *Parser, byte: u8) InnerError { - if (byte == 0) { - return self.fail("unexpected EOF", .{}); - } else { - return self.fail("unexpected byte: '{c}'", .{byte}); - } - } - - fn fail(self: *Parser, comptime format: []const u8, args: anytype) InnerError { - @setCold(true); - self.error_msg = ErrorMsg{ - .byte_offset = self.i, - .msg = try std.fmt.allocPrint(&self.arena.allocator, format, args), - }; - return error.ParseFailure; - } - - fn parseInstruction(self: *Parser, body_ctx: ?*Body, name: []const u8) InnerError!*Decl { - const contents_start = self.i; - const fn_name = try skipToAndOver(self, '('); - inline for (@typeInfo(Inst.Tag).Enum.fields) |field| { - if (mem.eql(u8, field.name, fn_name)) { - const tag = @field(Inst.Tag, field.name); - return parseInstructionGeneric(self, field.name, tag.Type(), tag, body_ctx, name, contents_start); - } - } - return self.fail("unknown instruction '{s}'", .{fn_name}); - } - - fn parseInstructionGeneric( - self: *Parser, - comptime fn_name: []const u8, - comptime InstType: type, - tag: Inst.Tag, - body_ctx: ?*Body, - inst_name: []const u8, - contents_start: usize, - ) InnerError!*Decl { - const inst_specific = try self.arena.allocator.create(InstType); - inst_specific.base = .{ - .src = self.i, - .tag = tag, - }; - - if (InstType == Inst.Block) { - try self.block_table.put(inst_name, inst_specific); - } else if (InstType == Inst.Loop) { - try self.loop_table.put(inst_name, inst_specific); - } - - if (@hasField(InstType, "ty")) { - inst_specific.ty = opt_type orelse { - return self.fail("instruction '" ++ fn_name ++ "' requires type", .{}); - }; - } - - const Positionals = @TypeOf(inst_specific.positionals); - inline for (@typeInfo(Positionals).Struct.fields) |arg_field| { - if (self.source[self.i] == ',') { - self.i += 1; - skipSpace(self); - } else if (self.source[self.i] == ')') { - return self.fail("expected positional parameter '{s}'", .{arg_field.name}); - } - @field(inst_specific.positionals, arg_field.name) = try parseParameterGeneric( - self, - arg_field.field_type, - body_ctx, - ); - skipSpace(self); - } - - const KW_Args = @TypeOf(inst_specific.kw_args); - inst_specific.kw_args = .{}; // assign defaults - skipSpace(self); - while (eatByte(self, ',')) { - skipSpace(self); - const name = try skipToAndOver(self, '='); - inline for (@typeInfo(KW_Args).Struct.fields) |arg_field| { - const field_name = arg_field.name; - if (mem.eql(u8, name, field_name)) { - const NonOptional = switch (@typeInfo(arg_field.field_type)) { - .Optional => |info| info.child, - else => arg_field.field_type, - }; - @field(inst_specific.kw_args, field_name) = try parseParameterGeneric(self, NonOptional, body_ctx); - break; - } - } else { - return self.fail("unrecognized keyword parameter: '{s}'", .{name}); - } - skipSpace(self); - } - try requireEatBytes(self, ")"); - - const decl = try self.arena.allocator.create(Decl); - decl.* = .{ - .name = inst_name, - .contents_hash = std.zig.hashSrc(self.source[contents_start..self.i]), - .inst = &inst_specific.base, - }; - - return decl; - } - - fn parseParameterGeneric(self: *Parser, comptime T: type, body_ctx: ?*Body) !T { - if (@typeInfo(T) == .Enum) { - const start = self.i; - while (true) : (self.i += 1) switch (self.source[self.i]) { - ' ', '\n', ',', ')' => { - const enum_name = self.source[start..self.i]; - return std.meta.stringToEnum(T, enum_name) orelse { - return self.fail("tag '{s}' not a member of enum '{s}'", .{ enum_name, @typeName(T) }); - }; - }, - 0 => return self.failByte(0), - else => continue, - }; - } - switch (T) { - Module.Body => return parseBody(self, body_ctx), - bool => { - const bool_value = switch (self.source[self.i]) { - '0' => false, - '1' => true, - else => |byte| return self.fail("expected '0' or '1' for boolean value, found {c}", .{byte}), - }; - self.i += 1; - return bool_value; - }, - []*Inst => { - try requireEatBytes(self, "["); - skipSpace(self); - if (eatByte(self, ']')) return &[0]*Inst{}; - - var instructions = std.ArrayList(*Inst).init(&self.arena.allocator); - while (true) { - skipSpace(self); - try instructions.append(try parseParameterInst(self, body_ctx)); - skipSpace(self); - if (!eatByte(self, ',')) break; - } - try requireEatBytes(self, "]"); - return instructions.toOwnedSlice(); - }, - *Inst => return parseParameterInst(self, body_ctx), - []u8, []const u8 => return self.parseStringLiteral(), - BigIntConst => return self.parseIntegerLiteral(), - usize => { - const big_int = try self.parseIntegerLiteral(); - return big_int.to(usize) catch |err| return self.fail("integer literal: {s}", .{@errorName(err)}); - }, - TypedValue => return self.fail("'const' is a special instruction; not legal in ZIR text", .{}), - *IrModule.Decl => return self.fail("'declval_in_module' is a special instruction; not legal in ZIR text", .{}), - *Inst.Block => { - const name = try self.parseStringLiteral(); - return self.block_table.get(name).?; - }, - *Inst.Loop => { - const name = try self.parseStringLiteral(); - return self.loop_table.get(name).?; - }, - [][]const u8 => { - try requireEatBytes(self, "["); - skipSpace(self); - if (eatByte(self, ']')) return &[0][]const u8{}; - - var strings = std.ArrayList([]const u8).init(&self.arena.allocator); - while (true) { - skipSpace(self); - try strings.append(try self.parseStringLiteral()); - skipSpace(self); - if (!eatByte(self, ',')) break; - } - try requireEatBytes(self, "]"); - return strings.toOwnedSlice(); - }, - []Inst.SwitchBr.Case => { - try requireEatBytes(self, "{"); - skipSpace(self); - if (eatByte(self, '}')) return &[0]Inst.SwitchBr.Case{}; - - var cases = std.ArrayList(Inst.SwitchBr.Case).init(&self.arena.allocator); - while (true) { - const cur = try cases.addOne(); - skipSpace(self); - cur.item = try self.parseParameterGeneric(*Inst, body_ctx); - skipSpace(self); - try requireEatBytes(self, "=>"); - cur.body = try self.parseBody(body_ctx); - skipSpace(self); - if (!eatByte(self, ',')) break; - } - skipSpace(self); - try requireEatBytes(self, "}"); - return cases.toOwnedSlice(); - }, - else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), - } - return self.fail("TODO parse parameter {s}", .{@typeName(T)}); - } - - fn parseParameterInst(self: *Parser, body_ctx: ?*Body) !*Inst { - const local_ref = switch (self.source[self.i]) { - '@' => false, - '%' => true, - else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), - }; - const map = if (local_ref) - if (body_ctx) |bc| - bc.name_map - else - return self.fail("referencing a % instruction in global scope", .{}) - else - self.global_name_map; - - self.i += 1; - const name_start = self.i; - while (true) : (self.i += 1) switch (self.source[self.i]) { - 0, ' ', '\n', ',', ')', ']' => break, - else => continue, - }; - const ident = self.source[name_start..self.i]; - return map.get(ident) orelse { - const bad_name = self.source[name_start - 1 .. self.i]; - const src = name_start - 1; - if (local_ref) { - self.i = src; - return self.fail("unrecognized identifier: {s}", .{bad_name}); - } else { - const declval = try self.arena.allocator.create(Inst.DeclVal); - declval.* = .{ - .base = .{ - .src = src, - .tag = Inst.DeclVal.base_tag, - }, - .positionals = .{ .name = ident }, - .kw_args = .{}, - }; - return &declval.base; - } - }; - } - - fn generateName(self: *Parser) ![]u8 { - const result = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${d}", .{self.unnamed_index}); - self.unnamed_index += 1; - return result; - } -}; - -pub fn emit(allocator: *Allocator, old_module: *IrModule) !Module { - var ctx: EmitZIR = .{ - .allocator = allocator, - .decls = .{}, - .arena = std.heap.ArenaAllocator.init(allocator), - .old_module = old_module, - .next_auto_name = 0, - .names = std.StringArrayHashMap(void).init(allocator), - .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), - .indent = 0, - .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), - .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator), - .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), - .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), - }; - errdefer ctx.metadata.deinit(); - errdefer ctx.body_metadata.deinit(); - defer ctx.block_table.deinit(); - defer ctx.loop_table.deinit(); - defer ctx.decls.deinit(allocator); - defer ctx.names.deinit(); - defer ctx.primitive_table.deinit(); - errdefer ctx.arena.deinit(); - - try ctx.emit(); - - return Module{ - .decls = ctx.decls.toOwnedSlice(allocator), - .arena = ctx.arena, - .metadata = ctx.metadata, - .body_metadata = ctx.body_metadata, - }; -} - /// For debugging purposes, prints a function representation to stderr. pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void { const allocator = old_module.gpa; @@ -2374,1052 +1856,14 @@ const DumpTzir = struct { } }; -const EmitZIR = struct { - allocator: *Allocator, - arena: std.heap.ArenaAllocator, - old_module: *const IrModule, - decls: std.ArrayListUnmanaged(*Decl), - names: std.StringArrayHashMap(void), - next_auto_name: usize, - primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), - indent: usize, - block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block), - loop_table: std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop), - metadata: std.AutoHashMap(*Inst, Module.MetaData), - body_metadata: std.AutoHashMap(*Module.Body, Module.BodyMetaData), - - fn emit(self: *EmitZIR) !void { - // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced - // by the hash table. - var src_decls = std.ArrayList(*IrModule.Decl).init(self.allocator); - defer src_decls.deinit(); - try src_decls.ensureCapacity(self.old_module.decl_table.items().len); - try self.decls.ensureCapacity(self.allocator, self.old_module.decl_table.items().len); - try self.names.ensureCapacity(self.old_module.decl_table.items().len); - - for (self.old_module.decl_table.items()) |entry| { - const decl = entry.value; - src_decls.appendAssumeCapacity(decl); - self.names.putAssumeCapacityNoClobber(mem.spanZ(decl.name), {}); - } - std.sort.sort(*IrModule.Decl, src_decls.items, {}, (struct { - fn lessThan(context: void, a: *IrModule.Decl, b: *IrModule.Decl) bool { - return a.src_index < b.src_index; - } - }).lessThan); - - // Emit all the decls. - for (src_decls.items) |ir_decl| { - switch (ir_decl.analysis) { - .unreferenced => continue, - - .complete => {}, - .codegen_failure => {}, // We still can emit the ZIR. - .codegen_failure_retryable => {}, // We still can emit the ZIR. - - .in_progress => unreachable, - .outdated => unreachable, - - .sema_failure, - .sema_failure_retryable, - .dependency_failure, - => if (self.old_module.failed_decls.get(ir_decl)) |err_msg| { - const fail_inst = try self.arena.allocator.create(Inst.UnOp); - fail_inst.* = .{ - .base = .{ - .src = ir_decl.src(), - .tag = .compileerror, - }, - .positionals = .{ - .operand = blk: { - const msg_str = try self.arena.allocator.dupe(u8, err_msg.msg); - - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = ir_decl.src(), - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = err_msg.msg, - }, - .kw_args = .{}, - }; - break :blk &str_inst.base; - }, - }, - .kw_args = .{}, - }; - const decl = try self.arena.allocator.create(Decl); - decl.* = .{ - .name = mem.spanZ(ir_decl.name), - .contents_hash = undefined, - .inst = &fail_inst.base, - }; - try self.decls.append(self.allocator, decl); - continue; - }, - } - if (self.old_module.export_owners.get(ir_decl)) |exports| { - for (exports) |module_export| { - const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name); - const export_inst = try self.arena.allocator.create(Inst.Export); - export_inst.* = .{ - .base = .{ - .src = module_export.src, - .tag = Inst.Export.base_tag, - }, - .positionals = .{ - .symbol_name = symbol_name.inst, - .decl_name = mem.spanZ(module_export.exported_decl.name), - }, - .kw_args = .{}, - }; - _ = try self.emitUnnamedDecl(&export_inst.base); - } - } - const new_decl = try self.emitTypedValue(ir_decl.src(), ir_decl.typed_value.most_recent.typed_value); - new_decl.name = try self.arena.allocator.dupe(u8, mem.spanZ(ir_decl.name)); - } - } - - const ZirBody = struct { - inst_table: *std.AutoHashMap(*ir.Inst, *Inst), - instructions: *std.ArrayList(*Inst), - }; - - fn resolveInst(self: *EmitZIR, new_body: ZirBody, inst: *ir.Inst) !*Inst { - if (inst.cast(ir.Inst.Constant)) |const_inst| { - const new_inst = if (const_inst.val.castTag(.function)) |func_pl| blk: { - const owner_decl = func_pl.data.owner_decl; - break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name)); - } else if (const_inst.val.castTag(.decl_ref)) |declref| blk: { - const decl_ref = try self.emitDeclRef(inst.src, declref.data); - try new_body.instructions.append(decl_ref); - break :blk decl_ref; - } else if (const_inst.val.castTag(.variable)) |var_pl| blk: { - const owner_decl = var_pl.data.owner_decl; - break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name)); - } else blk: { - break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst; - }; - _ = try new_body.inst_table.put(inst, new_inst); - return new_inst; - } else { - return new_body.inst_table.get(inst).?; - } - } - - fn emitDeclVal(self: *EmitZIR, src: usize, decl_name: []const u8) !*Inst { - const declval = try self.arena.allocator.create(Inst.DeclVal); - declval.* = .{ - .base = .{ - .src = src, - .tag = Inst.DeclVal.base_tag, - }, - .positionals = .{ .name = try self.arena.allocator.dupe(u8, decl_name) }, - .kw_args = .{}, - }; - return &declval.base; - } - - fn emitComptimeIntVal(self: *EmitZIR, src: usize, val: Value) !*Decl { - const big_int_space = try self.arena.allocator.create(Value.BigIntSpace); - const int_inst = try self.arena.allocator.create(Inst.Int); - int_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Int.base_tag, - }, - .positionals = .{ - .int = val.toBigInt(big_int_space), - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&int_inst.base); - } - - fn emitDeclRef(self: *EmitZIR, src: usize, module_decl: *IrModule.Decl) !*Inst { - const declref_inst = try self.arena.allocator.create(Inst.DeclRef); - declref_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.DeclRef.base_tag, - }, - .positionals = .{ - .name = mem.spanZ(module_decl.name), - }, - .kw_args = .{}, - }; - return &declref_inst.base; - } - - fn emitFn(self: *EmitZIR, module_fn: *IrModule.Fn, src: usize, ty: Type) Allocator.Error!*Decl { - var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); - defer inst_table.deinit(); - - var instructions = std.ArrayList(*Inst).init(self.allocator); - defer instructions.deinit(); - - switch (module_fn.state) { - .queued => unreachable, - .in_progress => unreachable, - .inline_only => unreachable, - .success => { - try self.emitBody(module_fn.body, &inst_table, &instructions); - }, - .sema_failure => { - const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?; - const fail_inst = try self.arena.allocator.create(Inst.UnOp); - fail_inst.* = .{ - .base = .{ - .src = src, - .tag = .compileerror, - }, - .positionals = .{ - .operand = blk: { - const msg_str = try self.arena.allocator.dupe(u8, err_msg.msg); - - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = msg_str, - }, - .kw_args = .{}, - }; - break :blk &str_inst.base; - }, - }, - .kw_args = .{}, - }; - try instructions.append(&fail_inst.base); - }, - .dependency_failure => { - const fail_inst = try self.arena.allocator.create(Inst.UnOp); - fail_inst.* = .{ - .base = .{ - .src = src, - .tag = .compileerror, - }, - .positionals = .{ - .operand = blk: { - const msg_str = try self.arena.allocator.dupe(u8, "depends on another failed Decl"); - - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = msg_str, - }, - .kw_args = .{}, - }; - break :blk &str_inst.base; - }, - }, - .kw_args = .{}, - }; - try instructions.append(&fail_inst.base); - }, - } - - const fn_type = try self.emitType(src, ty); - - const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); - mem.copy(*Inst, arena_instrs, instructions.items); - - const fn_inst = try self.arena.allocator.create(Inst.Fn); - fn_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Fn.base_tag, - }, - .positionals = .{ - .fn_type = fn_type.inst, - .body = .{ .instructions = arena_instrs }, - }, - .kw_args = .{ - .is_inline = module_fn.state == .inline_only, - }, - }; - return self.emitUnnamedDecl(&fn_inst.base); - } - - fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl { - const allocator = &self.arena.allocator; - if (typed_value.val.castTag(.decl_ref)) |decl_ref| { - const decl = decl_ref.data; - return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl)); - } else if (typed_value.val.castTag(.variable)) |variable| { - return self.emitTypedValue(src, .{ - .ty = typed_value.ty, - .val = variable.data.init, - }); - } - if (typed_value.val.isUndef()) { - const as_inst = try self.arena.allocator.create(Inst.BinOp); - as_inst.* = .{ - .base = .{ - .tag = .as, - .src = src, - }, - .positionals = .{ - .lhs = (try self.emitType(src, typed_value.ty)).inst, - .rhs = (try self.emitPrimitive(src, .@"undefined")).inst, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&as_inst.base); - } - switch (typed_value.ty.zigTypeTag()) { - .Pointer => { - const ptr_elem_type = typed_value.ty.elemType(); - switch (ptr_elem_type.zigTypeTag()) { - .Array => { - // TODO more checks to make sure this can be emitted as a string literal - //const array_elem_type = ptr_elem_type.elemType(); - //if (array_elem_type.eql(Type.initTag(.u8)) and - // ptr_elem_type.hasSentinel(Value.initTag(.zero))) - //{ - //} - const bytes = typed_value.val.toAllocatedBytes(allocator) catch |err| switch (err) { - error.AnalysisFail => unreachable, - else => |e| return e, - }; - return self.emitStringLiteral(src, bytes); - }, - else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {s}", .{@tagName(t)}), - } - }, - .ComptimeInt => return self.emitComptimeIntVal(src, typed_value.val), - .Int => { - const as_inst = try self.arena.allocator.create(Inst.BinOp); - as_inst.* = .{ - .base = .{ - .tag = .as, - .src = src, - }, - .positionals = .{ - .lhs = (try self.emitType(src, typed_value.ty)).inst, - .rhs = (try self.emitComptimeIntVal(src, typed_value.val)).inst, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&as_inst.base); - }, - .Type => { - const ty = try typed_value.val.toType(&self.arena.allocator); - return self.emitType(src, ty); - }, - .Fn => { - const module_fn = typed_value.val.castTag(.function).?.data; - return self.emitFn(module_fn, src, typed_value.ty); - }, - .Array => { - // TODO more checks to make sure this can be emitted as a string literal - //const array_elem_type = ptr_elem_type.elemType(); - //if (array_elem_type.eql(Type.initTag(.u8)) and - // ptr_elem_type.hasSentinel(Value.initTag(.zero))) - //{ - //} - const bytes = typed_value.val.toAllocatedBytes(allocator) catch |err| switch (err) { - error.AnalysisFail => unreachable, - else => |e| return e, - }; - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = bytes, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&str_inst.base); - }, - .Void => return self.emitPrimitive(src, .void_value), - .Bool => if (typed_value.val.toBool()) - return self.emitPrimitive(src, .@"true") - else - return self.emitPrimitive(src, .@"false"), - .EnumLiteral => { - const enum_literal = typed_value.val.castTag(.enum_literal).?; - const inst = try self.arena.allocator.create(Inst.Str); - inst.* = .{ - .base = .{ - .src = src, - .tag = .enum_literal, - }, - .positionals = .{ - .bytes = enum_literal.data, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&inst.base); - }, - else => |t| std.debug.panic("TODO implement emitTypedValue for {s}", .{@tagName(t)}), - } - } - - fn emitNoOp(self: *EmitZIR, src: usize, old_inst: *ir.Inst.NoOp, tag: Inst.Tag) Allocator.Error!*Inst { - const new_inst = try self.arena.allocator.create(Inst.NoOp); - new_inst.* = .{ - .base = .{ - .src = src, - .tag = tag, - }, - .positionals = .{}, - .kw_args = .{}, - }; - return &new_inst.base; - } - - fn emitUnOp( - self: *EmitZIR, - src: usize, - new_body: ZirBody, - old_inst: *ir.Inst.UnOp, - tag: Inst.Tag, - ) Allocator.Error!*Inst { - const new_inst = try self.arena.allocator.create(Inst.UnOp); - new_inst.* = .{ - .base = .{ - .src = src, - .tag = tag, - }, - .positionals = .{ - .operand = try self.resolveInst(new_body, old_inst.operand), - }, - .kw_args = .{}, - }; - return &new_inst.base; - } - - fn emitBinOp( - self: *EmitZIR, - src: usize, - new_body: ZirBody, - old_inst: *ir.Inst.BinOp, - tag: Inst.Tag, - ) Allocator.Error!*Inst { - const new_inst = try self.arena.allocator.create(Inst.BinOp); - new_inst.* = .{ - .base = .{ - .src = src, - .tag = tag, - }, - .positionals = .{ - .lhs = try self.resolveInst(new_body, old_inst.lhs), - .rhs = try self.resolveInst(new_body, old_inst.rhs), - }, - .kw_args = .{}, - }; - return &new_inst.base; - } - - fn emitCast( - self: *EmitZIR, - src: usize, - new_body: ZirBody, - old_inst: *ir.Inst.UnOp, - tag: Inst.Tag, - ) Allocator.Error!*Inst { - const new_inst = try self.arena.allocator.create(Inst.BinOp); - new_inst.* = .{ - .base = .{ - .src = src, - .tag = tag, - }, - .positionals = .{ - .lhs = (try self.emitType(old_inst.base.src, old_inst.base.ty)).inst, - .rhs = try self.resolveInst(new_body, old_inst.operand), - }, - .kw_args = .{}, - }; - return &new_inst.base; - } - - fn emitBody( - self: *EmitZIR, - body: ir.Body, - inst_table: *std.AutoHashMap(*ir.Inst, *Inst), - instructions: *std.ArrayList(*Inst), - ) Allocator.Error!void { - const new_body = ZirBody{ - .inst_table = inst_table, - .instructions = instructions, - }; - for (body.instructions) |inst| { - const new_inst = switch (inst.tag) { - .constant => unreachable, // excluded from function bodies - - .breakpoint => try self.emitNoOp(inst.src, inst.castTag(.breakpoint).?, .breakpoint), - .unreach => try self.emitNoOp(inst.src, inst.castTag(.unreach).?, .unreach_nocheck), - .retvoid => try self.emitNoOp(inst.src, inst.castTag(.retvoid).?, .returnvoid), - .dbg_stmt => try self.emitNoOp(inst.src, inst.castTag(.dbg_stmt).?, .dbg_stmt), - - .not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot), - .ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"), - .ptrtoint => try self.emitUnOp(inst.src, new_body, inst.castTag(.ptrtoint).?, .ptrtoint), - .isnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnull).?, .isnull), - .isnonnull => try self.emitUnOp(inst.src, new_body, inst.castTag(.isnonnull).?, .isnonnull), - .iserr => try self.emitUnOp(inst.src, new_body, inst.castTag(.iserr).?, .iserr), - .load => try self.emitUnOp(inst.src, new_body, inst.castTag(.load).?, .deref), - .ref => try self.emitUnOp(inst.src, new_body, inst.castTag(.ref).?, .ref), - .unwrap_optional => try self.emitUnOp(inst.src, new_body, inst.castTag(.unwrap_optional).?, .unwrap_optional_unsafe), - .wrap_optional => try self.emitCast(inst.src, new_body, inst.castTag(.wrap_optional).?, .as), - - .add => try self.emitBinOp(inst.src, new_body, inst.castTag(.add).?, .add), - .sub => try self.emitBinOp(inst.src, new_body, inst.castTag(.sub).?, .sub), - .store => try self.emitBinOp(inst.src, new_body, inst.castTag(.store).?, .store), - .cmp_lt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lt).?, .cmp_lt), - .cmp_lte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_lte).?, .cmp_lte), - .cmp_eq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_eq).?, .cmp_eq), - .cmp_gte => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gte).?, .cmp_gte), - .cmp_gt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gt).?, .cmp_gt), - .cmp_neq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_neq).?, .cmp_neq), - .booland => try self.emitBinOp(inst.src, new_body, inst.castTag(.booland).?, .booland), - .boolor => try self.emitBinOp(inst.src, new_body, inst.castTag(.boolor).?, .boolor), - .bitand => try self.emitBinOp(inst.src, new_body, inst.castTag(.bitand).?, .bitand), - .bitor => try self.emitBinOp(inst.src, new_body, inst.castTag(.bitor).?, .bitor), - .xor => try self.emitBinOp(inst.src, new_body, inst.castTag(.xor).?, .xor), - - .bitcast => try self.emitCast(inst.src, new_body, inst.castTag(.bitcast).?, .bitcast), - .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast), - .floatcast => try self.emitCast(inst.src, new_body, inst.castTag(.floatcast).?, .floatcast), - - .alloc => blk: { - const new_inst = try self.arena.allocator.create(Inst.UnOp); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = .alloc, - }, - .positionals = .{ - .operand = (try self.emitType(inst.src, inst.ty)).inst, - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - - .arg => blk: { - const old_inst = inst.castTag(.arg).?; - const new_inst = try self.arena.allocator.create(Inst.Arg); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = .arg, - }, - .positionals = .{ - .name = try self.arena.allocator.dupe(u8, mem.spanZ(old_inst.name)), - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - - .block => blk: { - const old_inst = inst.castTag(.block).?; - const new_inst = try self.arena.allocator.create(Inst.Block); - - try self.block_table.put(old_inst, new_inst); - - var block_body = std.ArrayList(*Inst).init(self.allocator); - defer block_body.deinit(); - - try self.emitBody(old_inst.body, inst_table, &block_body); - - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Block.base_tag, - }, - .positionals = .{ - .body = .{ .instructions = block_body.toOwnedSlice() }, - }, - .kw_args = .{}, - }; - - break :blk &new_inst.base; - }, - - .loop => blk: { - const old_inst = inst.castTag(.loop).?; - const new_inst = try self.arena.allocator.create(Inst.Loop); - - try self.loop_table.put(old_inst, new_inst); - - var loop_body = std.ArrayList(*Inst).init(self.allocator); - defer loop_body.deinit(); - - try self.emitBody(old_inst.body, inst_table, &loop_body); - - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Loop.base_tag, - }, - .positionals = .{ - .body = .{ .instructions = loop_body.toOwnedSlice() }, - }, - .kw_args = .{}, - }; - - break :blk &new_inst.base; - }, - - .brvoid => blk: { - const old_inst = inst.cast(ir.Inst.BrVoid).?; - const new_block = self.block_table.get(old_inst.block).?; - const new_inst = try self.arena.allocator.create(Inst.BreakVoid); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.BreakVoid.base_tag, - }, - .positionals = .{ - .block = new_block, - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - - .br => blk: { - const old_inst = inst.castTag(.br).?; - const new_block = self.block_table.get(old_inst.block).?; - const new_inst = try self.arena.allocator.create(Inst.Break); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Break.base_tag, - }, - .positionals = .{ - .block = new_block, - .operand = try self.resolveInst(new_body, old_inst.operand), - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - - .call => blk: { - const old_inst = inst.castTag(.call).?; - const new_inst = try self.arena.allocator.create(Inst.Call); - - const args = try self.arena.allocator.alloc(*Inst, old_inst.args.len); - for (args) |*elem, i| { - elem.* = try self.resolveInst(new_body, old_inst.args[i]); - } - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Call.base_tag, - }, - .positionals = .{ - .func = try self.resolveInst(new_body, old_inst.func), - .args = args, - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - - .assembly => blk: { - const old_inst = inst.castTag(.assembly).?; - const new_inst = try self.arena.allocator.create(Inst.Asm); - - const inputs = try self.arena.allocator.alloc(*Inst, old_inst.inputs.len); - for (inputs) |*elem, i| { - elem.* = (try self.emitStringLiteral(inst.src, old_inst.inputs[i])).inst; - } - - const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.clobbers.len); - for (clobbers) |*elem, i| { - elem.* = (try self.emitStringLiteral(inst.src, old_inst.clobbers[i])).inst; - } - - const args = try self.arena.allocator.alloc(*Inst, old_inst.args.len); - for (args) |*elem, i| { - elem.* = try self.resolveInst(new_body, old_inst.args[i]); - } - - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Asm.base_tag, - }, - .positionals = .{ - .asm_source = (try self.emitStringLiteral(inst.src, old_inst.asm_source)).inst, - .return_type = (try self.emitType(inst.src, inst.ty)).inst, - }, - .kw_args = .{ - .@"volatile" = old_inst.is_volatile, - .output = if (old_inst.output) |o| - (try self.emitStringLiteral(inst.src, o)).inst - else - null, - .inputs = inputs, - .clobbers = clobbers, - .args = args, - }, - }; - break :blk &new_inst.base; - }, - - .condbr => blk: { - const old_inst = inst.castTag(.condbr).?; - - var then_body = std.ArrayList(*Inst).init(self.allocator); - var else_body = std.ArrayList(*Inst).init(self.allocator); - - defer then_body.deinit(); - defer else_body.deinit(); - - const then_deaths = try self.arena.allocator.alloc(*Inst, old_inst.thenDeaths().len); - const else_deaths = try self.arena.allocator.alloc(*Inst, old_inst.elseDeaths().len); - - for (old_inst.thenDeaths()) |death, i| { - then_deaths[i] = try self.resolveInst(new_body, death); - } - for (old_inst.elseDeaths()) |death, i| { - else_deaths[i] = try self.resolveInst(new_body, death); - } - - try self.emitBody(old_inst.then_body, inst_table, &then_body); - try self.emitBody(old_inst.else_body, inst_table, &else_body); - - const new_inst = try self.arena.allocator.create(Inst.CondBr); - - try self.body_metadata.put(&new_inst.positionals.then_body, .{ .deaths = then_deaths }); - try self.body_metadata.put(&new_inst.positionals.else_body, .{ .deaths = else_deaths }); - - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.CondBr.base_tag, - }, - .positionals = .{ - .condition = try self.resolveInst(new_body, old_inst.condition), - .then_body = .{ .instructions = then_body.toOwnedSlice() }, - .else_body = .{ .instructions = else_body.toOwnedSlice() }, - }, - .kw_args = .{}, - }; - break :blk &new_inst.base; - }, - .switchbr => blk: { - const old_inst = inst.castTag(.switchbr).?; - const cases = try self.arena.allocator.alloc(Inst.SwitchBr.Case, old_inst.cases.len); - const new_inst = try self.arena.allocator.create(Inst.SwitchBr); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.SwitchBr.base_tag, - }, - .positionals = .{ - .target_ptr = try self.resolveInst(new_body, old_inst.target_ptr), - .cases = cases, - .items = &[_]*Inst{}, // TODO this should actually be populated - .else_body = undefined, // populated below - }, - .kw_args = .{}, - }; - - var body_tmp = std.ArrayList(*Inst).init(self.allocator); - defer body_tmp.deinit(); - - for (old_inst.cases) |*case, i| { - body_tmp.items.len = 0; - - const case_deaths = try self.arena.allocator.alloc(*Inst, old_inst.caseDeaths(i).len); - for (old_inst.caseDeaths(i)) |death, j| { - case_deaths[j] = try self.resolveInst(new_body, death); - } - try self.body_metadata.put(&cases[i].body, .{ .deaths = case_deaths }); - - try self.emitBody(case.body, inst_table, &body_tmp); - const item = (try self.emitTypedValue(inst.src, .{ - .ty = old_inst.target_ptr.ty.elemType(), - .val = case.item, - })).inst; - - cases[i] = .{ - .item = item, - .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) }, - }; - } - { // else - const else_deaths = try self.arena.allocator.alloc(*Inst, old_inst.elseDeaths().len); - for (old_inst.elseDeaths()) |death, j| { - else_deaths[j] = try self.resolveInst(new_body, death); - } - try self.body_metadata.put(&new_inst.positionals.else_body, .{ .deaths = else_deaths }); - - body_tmp.items.len = 0; - try self.emitBody(old_inst.else_body, inst_table, &body_tmp); - new_inst.positionals.else_body = .{ - .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items), - }; - } - - break :blk &new_inst.base; - }, - .varptr => @panic("TODO"), - }; - try self.metadata.put(new_inst, .{ - .deaths = inst.deaths, - .addr = @ptrToInt(inst), - }); - try instructions.append(new_inst); - try inst_table.put(inst, new_inst); - } - } - - fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Decl { - switch (ty.tag()) { - .i8 => return self.emitPrimitive(src, .i8), - .u8 => return self.emitPrimitive(src, .u8), - .i16 => return self.emitPrimitive(src, .i16), - .u16 => return self.emitPrimitive(src, .u16), - .i32 => return self.emitPrimitive(src, .i32), - .u32 => return self.emitPrimitive(src, .u32), - .i64 => return self.emitPrimitive(src, .i64), - .u64 => return self.emitPrimitive(src, .u64), - .isize => return self.emitPrimitive(src, .isize), - .usize => return self.emitPrimitive(src, .usize), - .c_short => return self.emitPrimitive(src, .c_short), - .c_ushort => return self.emitPrimitive(src, .c_ushort), - .c_int => return self.emitPrimitive(src, .c_int), - .c_uint => return self.emitPrimitive(src, .c_uint), - .c_long => return self.emitPrimitive(src, .c_long), - .c_ulong => return self.emitPrimitive(src, .c_ulong), - .c_longlong => return self.emitPrimitive(src, .c_longlong), - .c_ulonglong => return self.emitPrimitive(src, .c_ulonglong), - .c_longdouble => return self.emitPrimitive(src, .c_longdouble), - .c_void => return self.emitPrimitive(src, .c_void), - .f16 => return self.emitPrimitive(src, .f16), - .f32 => return self.emitPrimitive(src, .f32), - .f64 => return self.emitPrimitive(src, .f64), - .f128 => return self.emitPrimitive(src, .f128), - .anyerror => return self.emitPrimitive(src, .anyerror), - else => switch (ty.zigTypeTag()) { - .Bool => return self.emitPrimitive(src, .bool), - .Void => return self.emitPrimitive(src, .void), - .NoReturn => return self.emitPrimitive(src, .noreturn), - .Type => return self.emitPrimitive(src, .type), - .ComptimeInt => return self.emitPrimitive(src, .comptime_int), - .ComptimeFloat => return self.emitPrimitive(src, .comptime_float), - .Fn => { - const param_types = try self.allocator.alloc(Type, ty.fnParamLen()); - defer self.allocator.free(param_types); - - ty.fnParamTypes(param_types); - const emitted_params = try self.arena.allocator.alloc(*Inst, param_types.len); - for (param_types) |param_type, i| { - emitted_params[i] = (try self.emitType(src, param_type)).inst; - } - - const fntype_inst = try self.arena.allocator.create(Inst.FnType); - fntype_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.FnType.base_tag, - }, - .positionals = .{ - .param_types = emitted_params, - .return_type = (try self.emitType(src, ty.fnReturnType())).inst, - }, - .kw_args = .{ - .cc = ty.fnCallingConvention(), - }, - }; - return self.emitUnnamedDecl(&fntype_inst.base); - }, - .Int => { - const info = ty.intInfo(self.old_module.getTarget()); - const signed = try self.emitPrimitive(src, switch (info.signedness) { - .signed => .@"true", - .unsigned => .@"false", - }); - const bits_val = try Value.Tag.int_u64.create(&self.arena.allocator, info.bits); - const bits = try self.emitComptimeIntVal(src, bits_val); - const inttype_inst = try self.arena.allocator.create(Inst.IntType); - inttype_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.IntType.base_tag, - }, - .positionals = .{ - .signed = signed.inst, - .bits = bits.inst, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&inttype_inst.base); - }, - .Pointer => { - if (ty.isSinglePointer()) { - const inst = try self.arena.allocator.create(Inst.UnOp); - const tag: Inst.Tag = if (ty.isConstPtr()) .single_const_ptr_type else .single_mut_ptr_type; - inst.* = .{ - .base = .{ - .src = src, - .tag = tag, - }, - .positionals = .{ - .operand = (try self.emitType(src, ty.elemType())).inst, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&inst.base); - } else { - std.debug.panic("TODO implement emitType for {}", .{ty}); - } - }, - .Optional => { - var buf: Type.Payload.ElemType = undefined; - const inst = try self.arena.allocator.create(Inst.UnOp); - inst.* = .{ - .base = .{ - .src = src, - .tag = .optional_type, - }, - .positionals = .{ - .operand = (try self.emitType(src, ty.optionalChild(&buf))).inst, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&inst.base); - }, - .Array => { - var len_pl = Value.Payload.U64{ - .base = .{ .tag = .int_u64 }, - .data = ty.arrayLen(), - }; - const len = Value.initPayload(&len_pl.base); - - const inst = if (ty.sentinel()) |sentinel| blk: { - const inst = try self.arena.allocator.create(Inst.ArrayTypeSentinel); - inst.* = .{ - .base = .{ - .src = src, - .tag = .array_type, - }, - .positionals = .{ - .len = (try self.emitTypedValue(src, .{ - .ty = Type.initTag(.usize), - .val = len, - })).inst, - .sentinel = (try self.emitTypedValue(src, .{ - .ty = ty.elemType(), - .val = sentinel, - })).inst, - .elem_type = (try self.emitType(src, ty.elemType())).inst, - }, - .kw_args = .{}, - }; - break :blk &inst.base; - } else blk: { - const inst = try self.arena.allocator.create(Inst.BinOp); - inst.* = .{ - .base = .{ - .src = src, - .tag = .array_type, - }, - .positionals = .{ - .lhs = (try self.emitTypedValue(src, .{ - .ty = Type.initTag(.usize), - .val = len, - })).inst, - .rhs = (try self.emitType(src, ty.elemType())).inst, - }, - .kw_args = .{}, - }; - break :blk &inst.base; - }; - return self.emitUnnamedDecl(inst); - }, - else => std.debug.panic("TODO implement emitType for {}", .{ty}), - }, - } - } - - fn autoName(self: *EmitZIR) ![]u8 { - while (true) { - const proposed_name = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${d}", .{self.next_auto_name}); - self.next_auto_name += 1; - const gop = try self.names.getOrPut(proposed_name); - if (!gop.found_existing) { - gop.entry.value = {}; - return proposed_name; - } - } - } - - fn emitPrimitive(self: *EmitZIR, src: usize, tag: Inst.Primitive.Builtin) !*Decl { - const gop = try self.primitive_table.getOrPut(tag); - if (!gop.found_existing) { - const primitive_inst = try self.arena.allocator.create(Inst.Primitive); - primitive_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Primitive.base_tag, - }, - .positionals = .{ - .tag = tag, - }, - .kw_args = .{}, - }; - gop.entry.value = try self.emitUnnamedDecl(&primitive_inst.base); - } - return gop.entry.value; - } - - fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Decl { - const str_inst = try self.arena.allocator.create(Inst.Str); - str_inst.* = .{ - .base = .{ - .src = src, - .tag = Inst.Str.base_tag, - }, - .positionals = .{ - .bytes = str, - }, - .kw_args = .{}, - }; - return self.emitUnnamedDecl(&str_inst.base); - } - - fn emitUnnamedDecl(self: *EmitZIR, inst: *Inst) !*Decl { - const decl = try self.arena.allocator.create(Decl); - decl.* = .{ - .name = try self.autoName(), - .contents_hash = undefined, - .inst = inst, - }; - try self.decls.append(self.allocator, decl); - return decl; - } -}; - /// For debugging purposes, like dumpFn but for unanalyzed zir blocks pub fn dumpZir(allocator: *Allocator, kind: []const u8, decl_name: [*:0]const u8, instructions: []*Inst) !void { var fib = std.heap.FixedBufferAllocator.init(&[_]u8{}); var module = Module{ - .decls = &[_]*Decl{}, + .decls = &[_]*Module.Decl{}, .arena = std.heap.ArenaAllocator.init(&fib.allocator), .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(&fib.allocator), - .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(&fib.allocator), + .body_metadata = std.AutoHashMap(*Body, Module.BodyMetaData).init(&fib.allocator), }; var write = Writer{ .module = &module, diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 6302ab29e4..36eb5f4239 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -63,7 +63,6 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?), .declref_str => return analyzeInstDeclRefStr(mod, scope, old_inst.castTag(.declref_str).?), .declval => return analyzeInstDeclVal(mod, scope, old_inst.castTag(.declval).?), - .declval_in_module => return analyzeInstDeclValInModule(mod, scope, old_inst.castTag(.declval_in_module).?), .ensure_result_used => return analyzeInstEnsureResultUsed(mod, scope, old_inst.castTag(.ensure_result_used).?), .ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?), .ensure_indexable => return analyzeInstEnsureIndexable(mod, scope, old_inst.castTag(.ensure_indexable).?), @@ -166,7 +165,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! } } -pub fn analyzeBody(mod: *Module, block: *Scope.Block, body: zir.Module.Body) !void { +pub fn analyzeBody(mod: *Module, block: *Scope.Block, body: zir.Body) !void { const tracy = trace(@src()); defer tracy.end(); @@ -183,7 +182,7 @@ pub fn analyzeBodyValueAsType( mod: *Module, block_scope: *Scope.Block, zir_result_inst: *zir.Inst, - body: zir.Module.Body, + body: zir.Body, ) !Type { try analyzeBody(mod, block_scope, body); const result_inst = block_scope.inst_table.get(zir_result_inst).?; @@ -191,84 +190,6 @@ pub fn analyzeBodyValueAsType( return val.toType(block_scope.base.arena()); } -pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { - var decl_scope: Scope.DeclAnalysis = .{ - .decl = decl, - .arena = std.heap.ArenaAllocator.init(mod.gpa), - }; - errdefer decl_scope.arena.deinit(); - - decl.analysis = .in_progress; - - const typed_value = try analyzeConstInst(mod, &decl_scope.base, src_decl.inst); - const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); - - var prev_type_has_bits = false; - var type_changed = true; - - if (decl.typedValueManaged()) |tvm| { - prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); - type_changed = !tvm.typed_value.ty.eql(typed_value.ty); - - tvm.deinit(mod.gpa); - } - - arena_state.* = decl_scope.arena.state; - decl.typed_value = .{ - .most_recent = .{ - .typed_value = typed_value, - .arena = arena_state, - }, - }; - decl.analysis = .complete; - decl.generation = mod.generation; - if (typed_value.ty.hasCodeGenBits()) { - // We don't fully codegen the decl until later, but we do need to reserve a global - // offset table index for it. This allows us to codegen decls out of dependency order, - // increasing how many computations can be done in parallel. - try mod.comp.bin_file.allocateDeclIndexes(decl); - try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl }); - } else if (prev_type_has_bits) { - mod.comp.bin_file.freeDecl(decl); - } - - return type_changed; -} - -pub fn resolveZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { - const zir_module = mod.root_scope.cast(Scope.ZIRModule).?; - const entry = zir_module.contents.module.findDecl(src_decl.name).?; - return resolveZirDeclHavingIndex(mod, scope, src_decl, entry.index); -} - -fn resolveZirDeclHavingIndex(mod: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl { - const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name); - const decl = mod.decl_table.get(name_hash).?; - decl.src_index = src_index; - try mod.ensureDeclAnalyzed(decl); - return decl; -} - -/// Declares a dependency on the decl. -fn resolveCompleteZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { - const decl = try resolveZirDecl(mod, scope, src_decl); - switch (decl.analysis) { - .unreferenced => unreachable, - .in_progress => unreachable, - .outdated => unreachable, - - .dependency_failure, - .sema_failure, - .sema_failure_retryable, - .codegen_failure, - .codegen_failure_retryable, - => return error.AnalysisFail, - - .complete => {}, - } - return decl; -} - pub fn resolveInst(mod: *Module, scope: *Scope, zir_inst: *zir.Inst) InnerError!*Inst { const block = scope.cast(Scope.Block).?; return block.inst_table.get(zir_inst).?; // Instruction does not dominate all uses! @@ -640,22 +561,28 @@ fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) In } fn analyzeInstCompileLog(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileLog) InnerError!*Inst { - std.debug.print("| ", .{}); - for (inst.positionals.to_log) |item, i| { - const to_log = try resolveInst(mod, scope, item); - if (to_log.value()) |val| { - std.debug.print("{}", .{val}); + var managed = mod.compile_log_text.toManaged(mod.gpa); + defer mod.compile_log_text = managed.moveToUnmanaged(); + const writer = managed.writer(); + + for (inst.positionals.to_log) |arg_inst, i| { + if (i != 0) try writer.print(", ", .{}); + + const arg = try resolveInst(mod, scope, arg_inst); + if (arg.value()) |val| { + try writer.print("@as({}, {})", .{ arg.ty, val }); } else { - std.debug.print("(runtime value)", .{}); + try writer.print("@as({}, [runtime value])", .{arg.ty}); } - if (i != inst.positionals.to_log.len - 1) std.debug.print(", ", .{}); } - std.debug.print("\n", .{}); - if (!inst.kw_args.seen) { + try writer.print("\n", .{}); - // so that we do not give multiple compile errors if it gets evaled twice - inst.kw_args.seen = true; - try mod.failCompileLog(scope, inst.base.src); + const gop = try mod.compile_log_decls.getOrPut(mod.gpa, scope.ownerDecl().?); + if (!gop.found_existing) { + gop.entry.value = .{ + .file_scope = scope.getFileScope(), + .byte_offset = inst.base.src, + }; } return mod.constVoid(scope, inst.base.src); } @@ -705,7 +632,8 @@ fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, @@ -732,7 +660,8 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .label = null, @@ -744,13 +673,14 @@ fn analyzeInstBlockFlat(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_c try analyzeBody(mod, &child_block, inst.positionals.body); - try parent_block.instructions.appendSlice(mod.gpa, child_block.instructions.items); - - // comptime blocks won't generate any runtime values - if (child_block.instructions.items.len == 0) - return mod.constVoid(scope, inst.base.src); + // Move the analyzed instructions into the parent block arena. + const copied_instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items); + try parent_block.instructions.appendSlice(mod.gpa, copied_instructions); - return parent_block.instructions.items[parent_block.instructions.items.len - 1]; + // The result of a flat block is the last instruction. + const zir_inst_list = inst.positionals.body.instructions; + const last_zir_inst = zir_inst_list[zir_inst_list.len - 1]; + return resolveInst(mod, scope, last_zir_inst); } fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_comptime: bool) InnerError!*Inst { @@ -775,7 +705,8 @@ fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block, is_compt .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, // TODO @as here is working around a stage1 miscompilation bug :( @@ -890,22 +821,15 @@ fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr fn analyzeInstDeclRef(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); - return mod.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name); + return mod.analyzeDeclRef(scope, inst.base.src, inst.positionals.decl); } fn analyzeInstDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); - const decl = try analyzeDeclVal(mod, scope, inst); - const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl); - return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); -} - -fn analyzeInstDeclValInModule(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - const decl = inst.positionals.decl; - return mod.analyzeDeclRef(scope, inst.base.src, decl); + const decl_ref = try mod.analyzeDeclRef(scope, inst.base.src, inst.positionals.decl); + // TODO look into avoiding the call to analyzeDeref here + return mod.analyzeDeref(scope, inst.base.src, decl_ref, inst.base.src); } fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { @@ -1032,9 +956,8 @@ fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError .parent = null, .inst_table = &inst_table, .func = module_fn, - // Note that we pass the caller's Decl, not the callee. This causes - // compile errors to be attached (correctly) to the caller's Decl. - .decl = scope.decl().?, + .owner_decl = scope.ownerDecl().?, + .src_decl = module_fn.owner_decl, .instructions = .{}, .arena = scope.arena(), .label = null, @@ -1069,7 +992,7 @@ fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError! .state = if (fn_inst.kw_args.is_inline) .inline_only else .queued, .zir = fn_inst.positionals.body, .body = undefined, - .owner_decl = scope.decl().?, + .owner_decl = scope.ownerDecl().?, }; return mod.constInst(scope, fn_inst.base.src, .{ .ty = fn_type, @@ -1391,7 +1314,7 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr return mod.analyzeDeclRef(scope, fieldptr.base.src, decl); } - if (&container_scope.file_scope.base == mod.root_scope) { + if (container_scope.file_scope == mod.root_scope) { return mod.fail(scope, fieldptr.base.src, "root source file has no member called '{s}'", .{field_name}); } else { return mod.fail(scope, fieldptr.base.src, "container '{}' has no member called '{s}'", .{ child_type, field_name }); @@ -1606,7 +1529,8 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, @@ -2182,7 +2106,8 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, @@ -2196,7 +2121,8 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE .parent = parent_block, .inst_table = parent_block.inst_table, .func = parent_block.func, - .decl = parent_block.decl, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, .instructions = .{}, .arena = parent_block.arena, .inlining = parent_block.inlining, @@ -2294,17 +2220,6 @@ fn analyzeBreak( } else unreachable; } -fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl { - const decl_name = inst.positionals.name; - const zir_module = scope.namespace().cast(Scope.ZIRModule).?; - const src_decl = zir_module.contents.module.findDecl(decl_name) orelse - return mod.fail(scope, inst.base.src, "use of undeclared identifier '{s}'", .{decl_name}); - - const decl = try resolveCompleteZirDecl(mod, scope, src_decl.decl); - - return decl; -} - fn analyzeInstSimplePtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, mutable: bool, size: std.builtin.TypeInfo.Pointer.Size) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); diff --git a/test/stage2/test.zig b/test/stage2/test.zig index f25f07adbf..cb5e9bebbb 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -36,7 +36,7 @@ pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exe("hello world with updates", linux_x64); - case.addError("", &[_][]const u8{"no entry point found"}); + case.addError("", &[_][]const u8{"error: no entry point found"}); // Incorrect return type case.addError( @@ -147,7 +147,7 @@ pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exe("hello world with updates", macosx_x64); - case.addError("", &[_][]const u8{"no entry point found"}); + case.addError("", &[_][]const u8{"error: no entry point found"}); // Incorrect return type case.addError( @@ -1243,24 +1243,46 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &[_][]const u8{":3:9: error: redefinition of 'testing'"}); } - ctx.compileError("compileLog", linux_x64, - \\export fn _start() noreturn { - \\ const b = true; - \\ var f: u32 = 1; - \\ @compileLog(b, 20, f, x); - \\ @compileLog(1000); - \\ var bruh: usize = true; - \\ unreachable; - \\} - \\fn x() void {} - , &[_][]const u8{ - ":4:3: error: found compile log statement", - ":5:3: error: found compile log statement", - ":6:21: error: expected usize, found bool", - }); - // TODO if this is here it invalidates the compile error checker: - // "| true, 20, (runtime value), (function)" - // "| 1000" + + { + // TODO make the test harness support checking the compile log output too + var case = ctx.obj("@compileLog", linux_x64); + // The other compile error prevents emission of a "found compile log" statement. + case.addError( + \\export fn _start() noreturn { + \\ const b = true; + \\ var f: u32 = 1; + \\ @compileLog(b, 20, f, x); + \\ @compileLog(1000); + \\ var bruh: usize = true; + \\ unreachable; + \\} + \\export fn other() void { + \\ @compileLog(1234); + \\} + \\fn x() void {} + , &[_][]const u8{ + ":6:23: error: expected usize, found bool", + }); + + // Now only compile log statements remain. One per Decl. + case.addError( + \\export fn _start() noreturn { + \\ const b = true; + \\ var f: u32 = 1; + \\ @compileLog(b, 20, f, x); + \\ @compileLog(1000); + \\ unreachable; + \\} + \\export fn other() void { + \\ @compileLog(1234); + \\} + \\fn x() void {} + , &[_][]const u8{ + ":11:8: error: found compile log statement", + ":4:5: note: also here", + }); + } { var case = ctx.obj("extern variable has no type", linux_x64); -- cgit v1.2.3 From b25cf7db0253f331f44b279fcbcdf71faa1afb92 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 17 Jan 2021 11:26:02 +0100 Subject: stage2 aarch64: add basic function pro/epilogue Fix typo in `nop` implementation. Simplify `aarch64` macOS tests. --- src/codegen.zig | 66 ++++++++++++++++++++++++++++++- src/codegen/aarch64.zig | 2 +- test/stage2/aarch64.zig | 101 +++++++++++------------------------------------- 3 files changed, 88 insertions(+), 81 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen.zig b/src/codegen.zig index e491178549..1478ede6ff 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -637,6 +637,67 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.dbgSetEpilogueBegin(); } }, + .aarch64, .aarch64_be, .aarch64_32 => { + const cc = self.fn_type.fnCallingConvention(); + if (cc != .Naked) { + // TODO Finish function prologue and epilogue for aarch64. + // Reserve the stack for local variables, etc. + + // stp fp, lr, [sp, #-16]! + writeInt(u32, try self.code.addManyAsArray(4), Instruction.stp( + .x29, + .x30, + Register.sp, + Instruction.LoadStorePairOffset.pre_index(-16), + ).toU32()); + + try self.dbgSetPrologueEnd(); + + try self.genBody(self.mod_fn.body); + + try self.dbgSetEpilogueBegin(); + + // exitlude jumps + if (self.exitlude_jump_relocs.items.len == 1) { + // There is only one relocation. Hence, + // this relocation must be at the end of + // the code. Therefore, we can just delete + // the space initially reserved for the + // jump + self.code.items.len -= 4; + } else for (self.exitlude_jump_relocs.items) |jmp_reloc| { + const amt = @intCast(i32, self.code.items.len) - @intCast(i32, jmp_reloc + 8); + if (amt == -4) { + // This return is at the end of the + // code block. We can't just delete + // the space because there may be + // other jumps we already relocated to + // the address. Instead, insert a nop + writeInt(u32, self.code.items[jmp_reloc..][0..4], Instruction.nop().toU32()); + } else { + if (math.cast(i28, amt)) |offset| { + writeInt(u32, self.code.items[jmp_reloc..][0..4], Instruction.b(offset).toU32()); + } else |err| { + return self.failSymbol("exitlude jump is too large", .{}); + } + } + } + + // ldp fp, lr, [sp], #16 + writeInt(u32, try self.code.addManyAsArray(4), Instruction.ldp( + .x29, + .x30, + Register.sp, + Instruction.LoadStorePairOffset.post_index(16), + ).toU32()); + // ret lr + writeInt(u32, try self.code.addManyAsArray(4), Instruction.ret(null).toU32()); + } else { + try self.dbgSetPrologueEnd(); + try self.genBody(self.mod_fn.body); + try self.dbgSetEpilogueBegin(); + } + }, else => { try self.dbgSetPrologueEnd(); try self.genBody(self.mod_fn.body); @@ -1962,8 +2023,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4); }, .aarch64 => { - // TODO: relocations - writeInt(u32, try self.code.addManyAsArray(4), Instruction.ret(null).toU32()); + // Just add space for an instruction, patch this later + try self.code.resize(self.code.items.len + 4); + try self.exitlude_jump_relocs.append(self.gpa, self.code.items.len - 4); }, else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), } diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 5fba1ea7e1..5999f8888c 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -814,7 +814,7 @@ pub const Instruction = union(enum) { // Nop pub fn nop() Instruction { - return Instruction{ .NoOperation = {} }; + return Instruction{ .NoOperation = .{} }; } // Logical (shifted register) diff --git a/test/stage2/aarch64.zig b/test/stage2/aarch64.zig index fb2b240c4d..6c283f8e9f 100644 --- a/test/stage2/aarch64.zig +++ b/test/stage2/aarch64.zig @@ -17,97 +17,60 @@ pub fn addCases(ctx: *TestContext) !void { // Regular old hello world case.addCompareOutput( + \\extern "c" fn write(usize, usize, usize) void; + \\extern "c" fn exit(usize) noreturn; + \\ \\export fn _start() noreturn { \\ print(); \\ - \\ exit(); + \\ exit(0); \\} \\ \\fn print() void { - \\ asm volatile ("svc #0x80" - \\ : - \\ : [number] "{x16}" (4), - \\ [arg1] "{x0}" (1), - \\ [arg2] "{x1}" (@ptrToInt("Hello, World!\n")), - \\ [arg3] "{x2}" (14) - \\ : "memory" - \\ ); - \\ return; - \\} - \\ - \\fn exit() noreturn { - \\ asm volatile ("svc #0x80" - \\ : - \\ : [number] "{x16}" (1), - \\ [arg1] "{x0}" (0) - \\ : "memory" - \\ ); - \\ unreachable; + \\ const msg = @ptrToInt("Hello, World!\n"); + \\ const len = 14; + \\ write(1, msg, len); \\} , "Hello, World!\n", ); + // Now change the message only case.addCompareOutput( + \\extern "c" fn write(usize, usize, usize) void; + \\extern "c" fn exit(usize) noreturn; + \\ \\export fn _start() noreturn { \\ print(); \\ - \\ exit(); + \\ exit(0); \\} \\ \\fn print() void { - \\ asm volatile ("svc #0x80" - \\ : - \\ : [number] "{x16}" (4), - \\ [arg1] "{x0}" (1), - \\ [arg2] "{x1}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")), - \\ [arg3] "{x2}" (104) - \\ : "memory" - \\ ); - \\ return; - \\} - \\ - \\fn exit() noreturn { - \\ asm volatile ("svc #0x80" - \\ : - \\ : [number] "{x16}" (1), - \\ [arg1] "{x0}" (0) - \\ : "memory" - \\ ); - \\ unreachable; + \\ const msg = @ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n"); + \\ const len = 104; + \\ write(1, msg, len); \\} , "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n", ); + // Now we print it twice. case.addCompareOutput( + \\extern "c" fn write(usize, usize, usize) void; + \\extern "c" fn exit(usize) noreturn; + \\ \\export fn _start() noreturn { \\ print(); \\ print(); \\ - \\ exit(); + \\ exit(0); \\} \\ \\fn print() void { - \\ asm volatile ("svc #0x80" - \\ : - \\ : [number] "{x16}" (4), - \\ [arg1] "{x0}" (1), - \\ [arg2] "{x1}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")), - \\ [arg3] "{x2}" (104) - \\ : "memory" - \\ ); - \\ return; - \\} - \\ - \\fn exit() noreturn { - \\ asm volatile ("svc #0x80" - \\ : - \\ : [number] "{x16}" (1), - \\ [arg1] "{x0}" (0) - \\ : "memory" - \\ ); - \\ unreachable; + \\ const msg = @ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n"); + \\ const len = 104; + \\ write(1, msg, len); \\} , \\What is up? This is a longer message that will force the data to be relocated in virtual address space. @@ -200,24 +163,6 @@ pub fn addCases(ctx: *TestContext) !void { ); } - { - var case = ctx.exe("hello world linked to libc", macos_aarch64); - - // TODO rewrite this test once we handle more int conversions and return args. - case.addCompareOutput( - \\extern "c" fn write(usize, usize, usize) void; - \\extern "c" fn exit(usize) noreturn; - \\ - \\export fn _start() noreturn { - \\ write(1, @ptrToInt("Hello,"), 6); - \\ write(1, @ptrToInt(" World!\n,"), 8); - \\ exit(0); - \\} - , - "Hello, World!\n", - ); - } - { var case = ctx.exe("only libc exit", macos_aarch64); -- cgit v1.2.3 From ea6f3e3fd1ea47f7e5234852dccd7e988ba42b6d Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sun, 17 Jan 2021 22:40:14 +0100 Subject: stage2 AArch64: add add/subtract (immediate) instructions --- src/codegen/aarch64.zig | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) (limited to 'src/codegen') diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index 5999f8888c..e7860790f4 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -274,6 +274,16 @@ pub const Instruction = union(enum) { opc: u2, sf: u1, }, + AddSubtractImmediate: packed struct { + rd: u5, + rn: u5, + imm12: u12, + sh: u1, + fixed: u6 = 0b100010, + s: u1, + op: u1, + sf: u1, + }, pub const Shift = struct { shift: Type = .lsl, @@ -304,6 +314,7 @@ pub const Instruction = union(enum) { .UnconditionalBranchImmediate => |v| @bitCast(u32, v), .NoOperation => |v| @bitCast(u32, v), .LogicalShiftedRegister => |v| @bitCast(u32, v), + .AddSubtractImmediate => |v| @bitCast(u32, v), }; } @@ -671,6 +682,31 @@ pub const Instruction = union(enum) { } } + fn addSubtractImmediate( + op: u1, + s: u1, + rd: Register, + rn: Register, + imm12: u12, + shift: bool, + ) Instruction { + return Instruction{ + .AddSubtractImmediate = .{ + .rd = rd.id(), + .rn = rn.id(), + .imm12 = imm12, + .sh = @boolToInt(shift), + .s = s, + .op = op, + .sf = switch (rd.size()) { + 32 => 0b0, + 64 => 0b1, + else => unreachable, // unexpected register size + }, + }, + }; + } + // Helper functions for assembly syntax functions // Move wide (immediate) @@ -850,6 +886,24 @@ pub const Instruction = union(enum) { pub fn bics(rd: Register, rn: Register, rm: Register, shift: Shift) Instruction { return logicalShiftedRegister(0b11, 0b1, shift, rd, rn, rm); } + + // Add/subtract (immediate) + + pub fn add(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { + return addSubtractImmediate(0b0, 0b0, rd, rn, imm, shift); + } + + pub fn adds(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { + return addSubtractImmediate(0b0, 0b1, rd, rn, imm, shift); + } + + pub fn sub(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { + return addSubtractImmediate(0b1, 0b0, rd, rn, imm, shift); + } + + pub fn subs(rd: Register, rn: Register, imm: u12, shift: bool) Instruction { + return addSubtractImmediate(0b1, 0b1, rd, rn, imm, shift); + } }; test "" { @@ -979,6 +1033,14 @@ test "serialize instructions" { .inst = Instruction.@"and"(.x0, .x4, .x2, .{ .shift = .lsl, .amount = 0x8 }), .expected = 0b1_00_01010_00_0_00010_001000_00100_00000, }, + .{ // add x0, x10, #10 + .inst = Instruction.add(.x0, .x10, 10, false), + .expected = 0b1_0_0_100010_0_0000_0000_1010_01010_00000, + }, + .{ // subs x0, x5, #11, lsl #12 + .inst = Instruction.subs(.x0, .x5, 11, true), + .expected = 0b1_1_1_100010_1_0000_0000_1011_00101_00000, + }, }; for (testcases) |case| { -- cgit v1.2.3 From 1a05b545205863c6bf9cbdee9157ad0c440300ca Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 18 Jan 2021 14:12:05 -0700 Subject: update wasm backend to match new Module API Fixes a logical merge conflict that I didn't notice before. --- src/codegen/wasm.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 70e9de7baf..cbb2a42189 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -57,16 +57,19 @@ pub const Context = struct { /// will have the index that comes after the last argument's index local_index: u32 = 0, /// If codegen fails, an error messages will be allocated and saved in `err_msg` - err_msg: *Compilation.ErrorMsg, + err_msg: *Module.ErrorMsg, const InnerError = error{ OutOfMemory, CodegenFail, }; - /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig + /// Sets `err_msg` on `Context` and returns `error.CodegenFail` which is caught in link/Wasm.zig fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError { - self.err_msg = try Compilation.ErrorMsg.create(self.gpa, src, fmt, args); + self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ + .file_scope = self.decl.getFileScope(), + .byte_offset = src, + }, fmt, args); return error.CodegenFail; } -- cgit v1.2.3 From 9a6babf482047042d71d3d390933e08b7fb7c925 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 19 Jan 2021 00:38:39 +0100 Subject: SPIR-V: Add generated specification --- src/codegen/spirv/spec.zig | 1645 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1645 insertions(+) create mode 100644 src/codegen/spirv/spec.zig (limited to 'src/codegen') diff --git a/src/codegen/spirv/spec.zig b/src/codegen/spirv/spec.zig new file mode 100644 index 0000000000..ceb62f1e5d --- /dev/null +++ b/src/codegen/spirv/spec.zig @@ -0,0 +1,1645 @@ +// Copyright (c) 2014-2020 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. +const Version = @import("builtin").Version; +pub const version = Version{ .major = 1, .minor = 5, .patch = 4 }; +pub const magic_number: u32 = 0x07230203; +pub const Opcode = extern enum(u16) { + OpNop = 0, + OpUndef = 1, + OpSourceContinued = 2, + OpSource = 3, + OpSourceExtension = 4, + OpName = 5, + OpMemberName = 6, + OpString = 7, + OpLine = 8, + OpExtension = 10, + OpExtInstImport = 11, + OpExtInst = 12, + OpMemoryModel = 14, + OpEntryPoint = 15, + OpExecutionMode = 16, + OpCapability = 17, + OpTypeVoid = 19, + OpTypeBool = 20, + OpTypeInt = 21, + OpTypeFloat = 22, + OpTypeVector = 23, + OpTypeMatrix = 24, + OpTypeImage = 25, + OpTypeSampler = 26, + OpTypeSampledImage = 27, + OpTypeArray = 28, + OpTypeRuntimeArray = 29, + OpTypeStruct = 30, + OpTypeOpaque = 31, + OpTypePointer = 32, + OpTypeFunction = 33, + OpTypeEvent = 34, + OpTypeDeviceEvent = 35, + OpTypeReserveId = 36, + OpTypeQueue = 37, + OpTypePipe = 38, + OpTypeForwardPointer = 39, + OpConstantTrue = 41, + OpConstantFalse = 42, + OpConstant = 43, + OpConstantComposite = 44, + OpConstantSampler = 45, + OpConstantNull = 46, + OpSpecConstantTrue = 48, + OpSpecConstantFalse = 49, + OpSpecConstant = 50, + OpSpecConstantComposite = 51, + OpSpecConstantOp = 52, + OpFunction = 54, + OpFunctionParameter = 55, + OpFunctionEnd = 56, + OpFunctionCall = 57, + OpVariable = 59, + OpImageTexelPointer = 60, + OpLoad = 61, + OpStore = 62, + OpCopyMemory = 63, + OpCopyMemorySized = 64, + OpAccessChain = 65, + OpInBoundsAccessChain = 66, + OpPtrAccessChain = 67, + OpArrayLength = 68, + OpGenericPtrMemSemantics = 69, + OpInBoundsPtrAccessChain = 70, + OpDecorate = 71, + OpMemberDecorate = 72, + OpDecorationGroup = 73, + OpGroupDecorate = 74, + OpGroupMemberDecorate = 75, + OpVectorExtractDynamic = 77, + OpVectorInsertDynamic = 78, + OpVectorShuffle = 79, + OpCompositeConstruct = 80, + OpCompositeExtract = 81, + OpCompositeInsert = 82, + OpCopyObject = 83, + OpTranspose = 84, + OpSampledImage = 86, + OpImageSampleImplicitLod = 87, + OpImageSampleExplicitLod = 88, + OpImageSampleDrefImplicitLod = 89, + OpImageSampleDrefExplicitLod = 90, + OpImageSampleProjImplicitLod = 91, + OpImageSampleProjExplicitLod = 92, + OpImageSampleProjDrefImplicitLod = 93, + OpImageSampleProjDrefExplicitLod = 94, + OpImageFetch = 95, + OpImageGather = 96, + OpImageDrefGather = 97, + OpImageRead = 98, + OpImageWrite = 99, + OpImage = 100, + OpImageQueryFormat = 101, + OpImageQueryOrder = 102, + OpImageQuerySizeLod = 103, + OpImageQuerySize = 104, + OpImageQueryLod = 105, + OpImageQueryLevels = 106, + OpImageQuerySamples = 107, + OpConvertFToU = 109, + OpConvertFToS = 110, + OpConvertSToF = 111, + OpConvertUToF = 112, + OpUConvert = 113, + OpSConvert = 114, + OpFConvert = 115, + OpQuantizeToF16 = 116, + OpConvertPtrToU = 117, + OpSatConvertSToU = 118, + OpSatConvertUToS = 119, + OpConvertUToPtr = 120, + OpPtrCastToGeneric = 121, + OpGenericCastToPtr = 122, + OpGenericCastToPtrExplicit = 123, + OpBitcast = 124, + OpSNegate = 126, + OpFNegate = 127, + OpIAdd = 128, + OpFAdd = 129, + OpISub = 130, + OpFSub = 131, + OpIMul = 132, + OpFMul = 133, + OpUDiv = 134, + OpSDiv = 135, + OpFDiv = 136, + OpUMod = 137, + OpSRem = 138, + OpSMod = 139, + OpFRem = 140, + OpFMod = 141, + OpVectorTimesScalar = 142, + OpMatrixTimesScalar = 143, + OpVectorTimesMatrix = 144, + OpMatrixTimesVector = 145, + OpMatrixTimesMatrix = 146, + OpOuterProduct = 147, + OpDot = 148, + OpIAddCarry = 149, + OpISubBorrow = 150, + OpUMulExtended = 151, + OpSMulExtended = 152, + OpAny = 154, + OpAll = 155, + OpIsNan = 156, + OpIsInf = 157, + OpIsFinite = 158, + OpIsNormal = 159, + OpSignBitSet = 160, + OpLessOrGreater = 161, + OpOrdered = 162, + OpUnordered = 163, + OpLogicalEqual = 164, + OpLogicalNotEqual = 165, + OpLogicalOr = 166, + OpLogicalAnd = 167, + OpLogicalNot = 168, + OpSelect = 169, + OpIEqual = 170, + OpINotEqual = 171, + OpUGreaterThan = 172, + OpSGreaterThan = 173, + OpUGreaterThanEqual = 174, + OpSGreaterThanEqual = 175, + OpULessThan = 176, + OpSLessThan = 177, + OpULessThanEqual = 178, + OpSLessThanEqual = 179, + OpFOrdEqual = 180, + OpFUnordEqual = 181, + OpFOrdNotEqual = 182, + OpFUnordNotEqual = 183, + OpFOrdLessThan = 184, + OpFUnordLessThan = 185, + OpFOrdGreaterThan = 186, + OpFUnordGreaterThan = 187, + OpFOrdLessThanEqual = 188, + OpFUnordLessThanEqual = 189, + OpFOrdGreaterThanEqual = 190, + OpFUnordGreaterThanEqual = 191, + OpShiftRightLogical = 194, + OpShiftRightArithmetic = 195, + OpShiftLeftLogical = 196, + OpBitwiseOr = 197, + OpBitwiseXor = 198, + OpBitwiseAnd = 199, + OpNot = 200, + OpBitFieldInsert = 201, + OpBitFieldSExtract = 202, + OpBitFieldUExtract = 203, + OpBitReverse = 204, + OpBitCount = 205, + OpDPdx = 207, + OpDPdy = 208, + OpFwidth = 209, + OpDPdxFine = 210, + OpDPdyFine = 211, + OpFwidthFine = 212, + OpDPdxCoarse = 213, + OpDPdyCoarse = 214, + OpFwidthCoarse = 215, + OpEmitVertex = 218, + OpEndPrimitive = 219, + OpEmitStreamVertex = 220, + OpEndStreamPrimitive = 221, + OpControlBarrier = 224, + OpMemoryBarrier = 225, + OpAtomicLoad = 227, + OpAtomicStore = 228, + OpAtomicExchange = 229, + OpAtomicCompareExchange = 230, + OpAtomicCompareExchangeWeak = 231, + OpAtomicIIncrement = 232, + OpAtomicIDecrement = 233, + OpAtomicIAdd = 234, + OpAtomicISub = 235, + OpAtomicSMin = 236, + OpAtomicUMin = 237, + OpAtomicSMax = 238, + OpAtomicUMax = 239, + OpAtomicAnd = 240, + OpAtomicOr = 241, + OpAtomicXor = 242, + OpPhi = 245, + OpLoopMerge = 246, + OpSelectionMerge = 247, + OpLabel = 248, + OpBranch = 249, + OpBranchConditional = 250, + OpSwitch = 251, + OpKill = 252, + OpReturn = 253, + OpReturnValue = 254, + OpUnreachable = 255, + OpLifetimeStart = 256, + OpLifetimeStop = 257, + OpGroupAsyncCopy = 259, + OpGroupWaitEvents = 260, + OpGroupAll = 261, + OpGroupAny = 262, + OpGroupBroadcast = 263, + OpGroupIAdd = 264, + OpGroupFAdd = 265, + OpGroupFMin = 266, + OpGroupUMin = 267, + OpGroupSMin = 268, + OpGroupFMax = 269, + OpGroupUMax = 270, + OpGroupSMax = 271, + OpReadPipe = 274, + OpWritePipe = 275, + OpReservedReadPipe = 276, + OpReservedWritePipe = 277, + OpReserveReadPipePackets = 278, + OpReserveWritePipePackets = 279, + OpCommitReadPipe = 280, + OpCommitWritePipe = 281, + OpIsValidReserveId = 282, + OpGetNumPipePackets = 283, + OpGetMaxPipePackets = 284, + OpGroupReserveReadPipePackets = 285, + OpGroupReserveWritePipePackets = 286, + OpGroupCommitReadPipe = 287, + OpGroupCommitWritePipe = 288, + OpEnqueueMarker = 291, + OpEnqueueKernel = 292, + OpGetKernelNDrangeSubGroupCount = 293, + OpGetKernelNDrangeMaxSubGroupSize = 294, + OpGetKernelWorkGroupSize = 295, + OpGetKernelPreferredWorkGroupSizeMultiple = 296, + OpRetainEvent = 297, + OpReleaseEvent = 298, + OpCreateUserEvent = 299, + OpIsValidEvent = 300, + OpSetUserEventStatus = 301, + OpCaptureEventProfilingInfo = 302, + OpGetDefaultQueue = 303, + OpBuildNDRange = 304, + OpImageSparseSampleImplicitLod = 305, + OpImageSparseSampleExplicitLod = 306, + OpImageSparseSampleDrefImplicitLod = 307, + OpImageSparseSampleDrefExplicitLod = 308, + OpImageSparseSampleProjImplicitLod = 309, + OpImageSparseSampleProjExplicitLod = 310, + OpImageSparseSampleProjDrefImplicitLod = 311, + OpImageSparseSampleProjDrefExplicitLod = 312, + OpImageSparseFetch = 313, + OpImageSparseGather = 314, + OpImageSparseDrefGather = 315, + OpImageSparseTexelsResident = 316, + OpNoLine = 317, + OpAtomicFlagTestAndSet = 318, + OpAtomicFlagClear = 319, + OpImageSparseRead = 320, + OpSizeOf = 321, + OpTypePipeStorage = 322, + OpConstantPipeStorage = 323, + OpCreatePipeFromPipeStorage = 324, + OpGetKernelLocalSizeForSubgroupCount = 325, + OpGetKernelMaxNumSubgroups = 326, + OpTypeNamedBarrier = 327, + OpNamedBarrierInitialize = 328, + OpMemoryNamedBarrier = 329, + OpModuleProcessed = 330, + OpExecutionModeId = 331, + OpDecorateId = 332, + OpGroupNonUniformElect = 333, + OpGroupNonUniformAll = 334, + OpGroupNonUniformAny = 335, + OpGroupNonUniformAllEqual = 336, + OpGroupNonUniformBroadcast = 337, + OpGroupNonUniformBroadcastFirst = 338, + OpGroupNonUniformBallot = 339, + OpGroupNonUniformInverseBallot = 340, + OpGroupNonUniformBallotBitExtract = 341, + OpGroupNonUniformBallotBitCount = 342, + OpGroupNonUniformBallotFindLSB = 343, + OpGroupNonUniformBallotFindMSB = 344, + OpGroupNonUniformShuffle = 345, + OpGroupNonUniformShuffleXor = 346, + OpGroupNonUniformShuffleUp = 347, + OpGroupNonUniformShuffleDown = 348, + OpGroupNonUniformIAdd = 349, + OpGroupNonUniformFAdd = 350, + OpGroupNonUniformIMul = 351, + OpGroupNonUniformFMul = 352, + OpGroupNonUniformSMin = 353, + OpGroupNonUniformUMin = 354, + OpGroupNonUniformFMin = 355, + OpGroupNonUniformSMax = 356, + OpGroupNonUniformUMax = 357, + OpGroupNonUniformFMax = 358, + OpGroupNonUniformBitwiseAnd = 359, + OpGroupNonUniformBitwiseOr = 360, + OpGroupNonUniformBitwiseXor = 361, + OpGroupNonUniformLogicalAnd = 362, + OpGroupNonUniformLogicalOr = 363, + OpGroupNonUniformLogicalXor = 364, + OpGroupNonUniformQuadBroadcast = 365, + OpGroupNonUniformQuadSwap = 366, + OpCopyLogical = 400, + OpPtrEqual = 401, + OpPtrNotEqual = 402, + OpPtrDiff = 403, + OpTerminateInvocation = 4416, + OpSubgroupBallotKHR = 4421, + OpSubgroupFirstInvocationKHR = 4422, + OpSubgroupAllKHR = 4428, + OpSubgroupAnyKHR = 4429, + OpSubgroupAllEqualKHR = 4430, + OpSubgroupReadInvocationKHR = 4432, + OpTraceRayKHR = 4445, + OpExecuteCallableKHR = 4446, + OpConvertUToAccelerationStructureKHR = 4447, + OpIgnoreIntersectionKHR = 4448, + OpTerminateRayKHR = 4449, + OpTypeRayQueryKHR = 4472, + OpRayQueryInitializeKHR = 4473, + OpRayQueryTerminateKHR = 4474, + OpRayQueryGenerateIntersectionKHR = 4475, + OpRayQueryConfirmIntersectionKHR = 4476, + OpRayQueryProceedKHR = 4477, + OpRayQueryGetIntersectionTypeKHR = 4479, + OpGroupIAddNonUniformAMD = 5000, + OpGroupFAddNonUniformAMD = 5001, + OpGroupFMinNonUniformAMD = 5002, + OpGroupUMinNonUniformAMD = 5003, + OpGroupSMinNonUniformAMD = 5004, + OpGroupFMaxNonUniformAMD = 5005, + OpGroupUMaxNonUniformAMD = 5006, + OpGroupSMaxNonUniformAMD = 5007, + OpFragmentMaskFetchAMD = 5011, + OpFragmentFetchAMD = 5012, + OpReadClockKHR = 5056, + OpImageSampleFootprintNV = 5283, + OpGroupNonUniformPartitionNV = 5296, + OpWritePackedPrimitiveIndices4x8NV = 5299, + OpReportIntersectionNV = 5334, + OpReportIntersectionKHR = 5334, + OpIgnoreIntersectionNV = 5335, + OpTerminateRayNV = 5336, + OpTraceNV = 5337, + OpTypeAccelerationStructureNV = 5341, + OpTypeAccelerationStructureKHR = 5341, + OpExecuteCallableNV = 5344, + OpTypeCooperativeMatrixNV = 5358, + OpCooperativeMatrixLoadNV = 5359, + OpCooperativeMatrixStoreNV = 5360, + OpCooperativeMatrixMulAddNV = 5361, + OpCooperativeMatrixLengthNV = 5362, + OpBeginInvocationInterlockEXT = 5364, + OpEndInvocationInterlockEXT = 5365, + OpDemoteToHelperInvocationEXT = 5380, + OpIsHelperInvocationEXT = 5381, + OpSubgroupShuffleINTEL = 5571, + OpSubgroupShuffleDownINTEL = 5572, + OpSubgroupShuffleUpINTEL = 5573, + OpSubgroupShuffleXorINTEL = 5574, + OpSubgroupBlockReadINTEL = 5575, + OpSubgroupBlockWriteINTEL = 5576, + OpSubgroupImageBlockReadINTEL = 5577, + OpSubgroupImageBlockWriteINTEL = 5578, + OpSubgroupImageMediaBlockReadINTEL = 5580, + OpSubgroupImageMediaBlockWriteINTEL = 5581, + OpUCountLeadingZerosINTEL = 5585, + OpUCountTrailingZerosINTEL = 5586, + OpAbsISubINTEL = 5587, + OpAbsUSubINTEL = 5588, + OpIAddSatINTEL = 5589, + OpUAddSatINTEL = 5590, + OpIAverageINTEL = 5591, + OpUAverageINTEL = 5592, + OpIAverageRoundedINTEL = 5593, + OpUAverageRoundedINTEL = 5594, + OpISubSatINTEL = 5595, + OpUSubSatINTEL = 5596, + OpIMul32x16INTEL = 5597, + OpUMul32x16INTEL = 5598, + OpFunctionPointerINTEL = 5600, + OpFunctionPointerCallINTEL = 5601, + OpDecorateString = 5632, + OpDecorateStringGOOGLE = 5632, + OpMemberDecorateString = 5633, + OpMemberDecorateStringGOOGLE = 5633, + OpVmeImageINTEL = 5699, + OpTypeVmeImageINTEL = 5700, + OpTypeAvcImePayloadINTEL = 5701, + OpTypeAvcRefPayloadINTEL = 5702, + OpTypeAvcSicPayloadINTEL = 5703, + OpTypeAvcMcePayloadINTEL = 5704, + OpTypeAvcMceResultINTEL = 5705, + OpTypeAvcImeResultINTEL = 5706, + OpTypeAvcImeResultSingleReferenceStreamoutINTEL = 5707, + OpTypeAvcImeResultDualReferenceStreamoutINTEL = 5708, + OpTypeAvcImeSingleReferenceStreaminINTEL = 5709, + OpTypeAvcImeDualReferenceStreaminINTEL = 5710, + OpTypeAvcRefResultINTEL = 5711, + OpTypeAvcSicResultINTEL = 5712, + OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL = 5713, + OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL = 5714, + OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL = 5715, + OpSubgroupAvcMceSetInterShapePenaltyINTEL = 5716, + OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL = 5717, + OpSubgroupAvcMceSetInterDirectionPenaltyINTEL = 5718, + OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL = 5719, + OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL = 5720, + OpSubgroupAvcMceGetDefaultHighPenaltyCostTableINTEL = 5721, + OpSubgroupAvcMceGetDefaultMediumPenaltyCostTableINTEL = 5722, + OpSubgroupAvcMceGetDefaultLowPenaltyCostTableINTEL = 5723, + OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL = 5724, + OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL = 5725, + OpSubgroupAvcMceGetDefaultNonDcLumaIntraPenaltyINTEL = 5726, + OpSubgroupAvcMceGetDefaultIntraChromaModeBasePenaltyINTEL = 5727, + OpSubgroupAvcMceSetAcOnlyHaarINTEL = 5728, + OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL = 5729, + OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL = 5730, + OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL = 5731, + OpSubgroupAvcMceConvertToImePayloadINTEL = 5732, + OpSubgroupAvcMceConvertToImeResultINTEL = 5733, + OpSubgroupAvcMceConvertToRefPayloadINTEL = 5734, + OpSubgroupAvcMceConvertToRefResultINTEL = 5735, + OpSubgroupAvcMceConvertToSicPayloadINTEL = 5736, + OpSubgroupAvcMceConvertToSicResultINTEL = 5737, + OpSubgroupAvcMceGetMotionVectorsINTEL = 5738, + OpSubgroupAvcMceGetInterDistortionsINTEL = 5739, + OpSubgroupAvcMceGetBestInterDistortionsINTEL = 5740, + OpSubgroupAvcMceGetInterMajorShapeINTEL = 5741, + OpSubgroupAvcMceGetInterMinorShapeINTEL = 5742, + OpSubgroupAvcMceGetInterDirectionsINTEL = 5743, + OpSubgroupAvcMceGetInterMotionVectorCountINTEL = 5744, + OpSubgroupAvcMceGetInterReferenceIdsINTEL = 5745, + OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL = 5746, + OpSubgroupAvcImeInitializeINTEL = 5747, + OpSubgroupAvcImeSetSingleReferenceINTEL = 5748, + OpSubgroupAvcImeSetDualReferenceINTEL = 5749, + OpSubgroupAvcImeRefWindowSizeINTEL = 5750, + OpSubgroupAvcImeAdjustRefOffsetINTEL = 5751, + OpSubgroupAvcImeConvertToMcePayloadINTEL = 5752, + OpSubgroupAvcImeSetMaxMotionVectorCountINTEL = 5753, + OpSubgroupAvcImeSetUnidirectionalMixDisableINTEL = 5754, + OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL = 5755, + OpSubgroupAvcImeSetWeightedSadINTEL = 5756, + OpSubgroupAvcImeEvaluateWithSingleReferenceINTEL = 5757, + OpSubgroupAvcImeEvaluateWithDualReferenceINTEL = 5758, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminINTEL = 5759, + OpSubgroupAvcImeEvaluateWithDualReferenceStreaminINTEL = 5760, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreamoutINTEL = 5761, + OpSubgroupAvcImeEvaluateWithDualReferenceStreamoutINTEL = 5762, + OpSubgroupAvcImeEvaluateWithSingleReferenceStreaminoutINTEL = 5763, + OpSubgroupAvcImeEvaluateWithDualReferenceStreaminoutINTEL = 5764, + OpSubgroupAvcImeConvertToMceResultINTEL = 5765, + OpSubgroupAvcImeGetSingleReferenceStreaminINTEL = 5766, + OpSubgroupAvcImeGetDualReferenceStreaminINTEL = 5767, + OpSubgroupAvcImeStripSingleReferenceStreamoutINTEL = 5768, + OpSubgroupAvcImeStripDualReferenceStreamoutINTEL = 5769, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL = 5770, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL = 5771, + OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL = 5772, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL = 5773, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL = 5774, + OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL = 5775, + OpSubgroupAvcImeGetBorderReachedINTEL = 5776, + OpSubgroupAvcImeGetTruncatedSearchIndicationINTEL = 5777, + OpSubgroupAvcImeGetUnidirectionalEarlySearchTerminationINTEL = 5778, + OpSubgroupAvcImeGetWeightingPatternMinimumMotionVectorINTEL = 5779, + OpSubgroupAvcImeGetWeightingPatternMinimumDistortionINTEL = 5780, + OpSubgroupAvcFmeInitializeINTEL = 5781, + OpSubgroupAvcBmeInitializeINTEL = 5782, + OpSubgroupAvcRefConvertToMcePayloadINTEL = 5783, + OpSubgroupAvcRefSetBidirectionalMixDisableINTEL = 5784, + OpSubgroupAvcRefSetBilinearFilterEnableINTEL = 5785, + OpSubgroupAvcRefEvaluateWithSingleReferenceINTEL = 5786, + OpSubgroupAvcRefEvaluateWithDualReferenceINTEL = 5787, + OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL = 5788, + OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL = 5789, + OpSubgroupAvcRefConvertToMceResultINTEL = 5790, + OpSubgroupAvcSicInitializeINTEL = 5791, + OpSubgroupAvcSicConfigureSkcINTEL = 5792, + OpSubgroupAvcSicConfigureIpeLumaINTEL = 5793, + OpSubgroupAvcSicConfigureIpeLumaChromaINTEL = 5794, + OpSubgroupAvcSicGetMotionVectorMaskINTEL = 5795, + OpSubgroupAvcSicConvertToMcePayloadINTEL = 5796, + OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL = 5797, + OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL = 5798, + OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL = 5799, + OpSubgroupAvcSicSetBilinearFilterEnableINTEL = 5800, + OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL = 5801, + OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL = 5802, + OpSubgroupAvcSicEvaluateIpeINTEL = 5803, + OpSubgroupAvcSicEvaluateWithSingleReferenceINTEL = 5804, + OpSubgroupAvcSicEvaluateWithDualReferenceINTEL = 5805, + OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL = 5806, + OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL = 5807, + OpSubgroupAvcSicConvertToMceResultINTEL = 5808, + OpSubgroupAvcSicGetIpeLumaShapeINTEL = 5809, + OpSubgroupAvcSicGetBestIpeLumaDistortionINTEL = 5810, + OpSubgroupAvcSicGetBestIpeChromaDistortionINTEL = 5811, + OpSubgroupAvcSicGetPackedIpeLumaModesINTEL = 5812, + OpSubgroupAvcSicGetIpeChromaModeINTEL = 5813, + OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL = 5814, + OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL = 5815, + OpSubgroupAvcSicGetInterRawSadsINTEL = 5816, + OpLoopControlINTEL = 5887, + OpReadPipeBlockingINTEL = 5946, + OpWritePipeBlockingINTEL = 5947, + OpFPGARegINTEL = 5949, + OpRayQueryGetRayTMinKHR = 6016, + OpRayQueryGetRayFlagsKHR = 6017, + OpRayQueryGetIntersectionTKHR = 6018, + OpRayQueryGetIntersectionInstanceCustomIndexKHR = 6019, + OpRayQueryGetIntersectionInstanceIdKHR = 6020, + OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR = 6021, + OpRayQueryGetIntersectionGeometryIndexKHR = 6022, + OpRayQueryGetIntersectionPrimitiveIndexKHR = 6023, + OpRayQueryGetIntersectionBarycentricsKHR = 6024, + OpRayQueryGetIntersectionFrontFaceKHR = 6025, + OpRayQueryGetIntersectionCandidateAABBOpaqueKHR = 6026, + OpRayQueryGetIntersectionObjectRayDirectionKHR = 6027, + OpRayQueryGetIntersectionObjectRayOriginKHR = 6028, + OpRayQueryGetWorldRayDirectionKHR = 6029, + OpRayQueryGetWorldRayOriginKHR = 6030, + OpRayQueryGetIntersectionObjectToWorldKHR = 6031, + OpRayQueryGetIntersectionWorldToObjectKHR = 6032, + OpAtomicFAddEXT = 6035, + _, +}; +pub const ImageOperands = packed struct { + Bias: bool align(@alignOf(u32)) = false, + Lod: bool = false, + Grad: bool = false, + ConstOffset: bool = false, + Offset: bool = false, + ConstOffsets: bool = false, + Sample: bool = false, + MinLod: bool = false, + MakeTexelAvailable: bool = false, + MakeTexelVisible: bool = false, + NonPrivateTexel: bool = false, + VolatileTexel: bool = false, + SignExtend: bool = false, + ZeroExtend: bool = false, + _reserved_bit_14: bool = false, + _reserved_bit_15: bool = false, + _reserved_bit_16: bool = false, + _reserved_bit_17: bool = false, + _reserved_bit_18: bool = false, + _reserved_bit_19: bool = false, + _reserved_bit_20: bool = false, + _reserved_bit_21: bool = false, + _reserved_bit_22: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const FPFastMathMode = packed struct { + NotNaN: bool align(@alignOf(u32)) = false, + NotInf: bool = false, + NSZ: bool = false, + AllowRecip: bool = false, + Fast: bool = false, + _reserved_bit_5: bool = false, + _reserved_bit_6: bool = false, + _reserved_bit_7: bool = false, + _reserved_bit_8: bool = false, + _reserved_bit_9: bool = false, + _reserved_bit_10: bool = false, + _reserved_bit_11: bool = false, + _reserved_bit_12: bool = false, + _reserved_bit_13: bool = false, + _reserved_bit_14: bool = false, + _reserved_bit_15: bool = false, + _reserved_bit_16: bool = false, + _reserved_bit_17: bool = false, + _reserved_bit_18: bool = false, + _reserved_bit_19: bool = false, + _reserved_bit_20: bool = false, + _reserved_bit_21: bool = false, + _reserved_bit_22: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const SelectionControl = packed struct { + Flatten: bool align(@alignOf(u32)) = false, + DontFlatten: bool = false, + _reserved_bit_2: bool = false, + _reserved_bit_3: bool = false, + _reserved_bit_4: bool = false, + _reserved_bit_5: bool = false, + _reserved_bit_6: bool = false, + _reserved_bit_7: bool = false, + _reserved_bit_8: bool = false, + _reserved_bit_9: bool = false, + _reserved_bit_10: bool = false, + _reserved_bit_11: bool = false, + _reserved_bit_12: bool = false, + _reserved_bit_13: bool = false, + _reserved_bit_14: bool = false, + _reserved_bit_15: bool = false, + _reserved_bit_16: bool = false, + _reserved_bit_17: bool = false, + _reserved_bit_18: bool = false, + _reserved_bit_19: bool = false, + _reserved_bit_20: bool = false, + _reserved_bit_21: bool = false, + _reserved_bit_22: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const LoopControl = packed struct { + Unroll: bool align(@alignOf(u32)) = false, + DontUnroll: bool = false, + DependencyInfinite: bool = false, + DependencyLength: bool = false, + MinIterations: bool = false, + MaxIterations: bool = false, + IterationMultiple: bool = false, + PeelCount: bool = false, + PartialCount: bool = false, + _reserved_bit_9: bool = false, + _reserved_bit_10: bool = false, + _reserved_bit_11: bool = false, + _reserved_bit_12: bool = false, + _reserved_bit_13: bool = false, + _reserved_bit_14: bool = false, + _reserved_bit_15: bool = false, + InitiationIntervalINTEL: bool = false, + MaxConcurrencyINTEL: bool = false, + DependencyArrayINTEL: bool = false, + PipelineEnableINTEL: bool = false, + LoopCoalesceINTEL: bool = false, + MaxInterleavingINTEL: bool = false, + SpeculatedIterationsINTEL: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const FunctionControl = packed struct { + Inline: bool align(@alignOf(u32)) = false, + DontInline: bool = false, + Pure: bool = false, + Const: bool = false, + _reserved_bit_4: bool = false, + _reserved_bit_5: bool = false, + _reserved_bit_6: bool = false, + _reserved_bit_7: bool = false, + _reserved_bit_8: bool = false, + _reserved_bit_9: bool = false, + _reserved_bit_10: bool = false, + _reserved_bit_11: bool = false, + _reserved_bit_12: bool = false, + _reserved_bit_13: bool = false, + _reserved_bit_14: bool = false, + _reserved_bit_15: bool = false, + _reserved_bit_16: bool = false, + _reserved_bit_17: bool = false, + _reserved_bit_18: bool = false, + _reserved_bit_19: bool = false, + _reserved_bit_20: bool = false, + _reserved_bit_21: bool = false, + _reserved_bit_22: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const MemorySemantics = packed struct { + _reserved_bit_0: bool align(@alignOf(u32)) = false, + Acquire: bool = false, + Release: bool = false, + AcquireRelease: bool = false, + SequentiallyConsistent: bool = false, + _reserved_bit_5: bool = false, + UniformMemory: bool = false, + SubgroupMemory: bool = false, + WorkgroupMemory: bool = false, + CrossWorkgroupMemory: bool = false, + AtomicCounterMemory: bool = false, + ImageMemory: bool = false, + OutputMemory: bool = false, + MakeAvailable: bool = false, + MakeVisible: bool = false, + Volatile: bool = false, + _reserved_bit_16: bool = false, + _reserved_bit_17: bool = false, + _reserved_bit_18: bool = false, + _reserved_bit_19: bool = false, + _reserved_bit_20: bool = false, + _reserved_bit_21: bool = false, + _reserved_bit_22: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const MemoryAccess = packed struct { + Volatile: bool align(@alignOf(u32)) = false, + Aligned: bool = false, + Nontemporal: bool = false, + MakePointerAvailable: bool = false, + MakePointerVisible: bool = false, + NonPrivatePointer: bool = false, + _reserved_bit_6: bool = false, + _reserved_bit_7: bool = false, + _reserved_bit_8: bool = false, + _reserved_bit_9: bool = false, + _reserved_bit_10: bool = false, + _reserved_bit_11: bool = false, + _reserved_bit_12: bool = false, + _reserved_bit_13: bool = false, + _reserved_bit_14: bool = false, + _reserved_bit_15: bool = false, + _reserved_bit_16: bool = false, + _reserved_bit_17: bool = false, + _reserved_bit_18: bool = false, + _reserved_bit_19: bool = false, + _reserved_bit_20: bool = false, + _reserved_bit_21: bool = false, + _reserved_bit_22: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const KernelProfilingInfo = packed struct { + CmdExecTime: bool align(@alignOf(u32)) = false, + _reserved_bit_1: bool = false, + _reserved_bit_2: bool = false, + _reserved_bit_3: bool = false, + _reserved_bit_4: bool = false, + _reserved_bit_5: bool = false, + _reserved_bit_6: bool = false, + _reserved_bit_7: bool = false, + _reserved_bit_8: bool = false, + _reserved_bit_9: bool = false, + _reserved_bit_10: bool = false, + _reserved_bit_11: bool = false, + _reserved_bit_12: bool = false, + _reserved_bit_13: bool = false, + _reserved_bit_14: bool = false, + _reserved_bit_15: bool = false, + _reserved_bit_16: bool = false, + _reserved_bit_17: bool = false, + _reserved_bit_18: bool = false, + _reserved_bit_19: bool = false, + _reserved_bit_20: bool = false, + _reserved_bit_21: bool = false, + _reserved_bit_22: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const RayFlags = packed struct { + OpaqueKHR: bool align(@alignOf(u32)) = false, + NoOpaqueKHR: bool = false, + TerminateOnFirstHitKHR: bool = false, + SkipClosestHitShaderKHR: bool = false, + CullBackFacingTrianglesKHR: bool = false, + CullFrontFacingTrianglesKHR: bool = false, + CullOpaqueKHR: bool = false, + CullNoOpaqueKHR: bool = false, + SkipTrianglesKHR: bool = false, + SkipAABBsKHR: bool = false, + _reserved_bit_10: bool = false, + _reserved_bit_11: bool = false, + _reserved_bit_12: bool = false, + _reserved_bit_13: bool = false, + _reserved_bit_14: bool = false, + _reserved_bit_15: bool = false, + _reserved_bit_16: bool = false, + _reserved_bit_17: bool = false, + _reserved_bit_18: bool = false, + _reserved_bit_19: bool = false, + _reserved_bit_20: bool = false, + _reserved_bit_21: bool = false, + _reserved_bit_22: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const FragmentShadingRate = packed struct { + Vertical2Pixels: bool align(@alignOf(u32)) = false, + Vertical4Pixels: bool = false, + Horizontal2Pixels: bool = false, + Horizontal4Pixels: bool = false, + _reserved_bit_4: bool = false, + _reserved_bit_5: bool = false, + _reserved_bit_6: bool = false, + _reserved_bit_7: bool = false, + _reserved_bit_8: bool = false, + _reserved_bit_9: bool = false, + _reserved_bit_10: bool = false, + _reserved_bit_11: bool = false, + _reserved_bit_12: bool = false, + _reserved_bit_13: bool = false, + _reserved_bit_14: bool = false, + _reserved_bit_15: bool = false, + _reserved_bit_16: bool = false, + _reserved_bit_17: bool = false, + _reserved_bit_18: bool = false, + _reserved_bit_19: bool = false, + _reserved_bit_20: bool = false, + _reserved_bit_21: bool = false, + _reserved_bit_22: bool = false, + _reserved_bit_23: bool = false, + _reserved_bit_24: bool = false, + _reserved_bit_25: bool = false, + _reserved_bit_26: bool = false, + _reserved_bit_27: bool = false, + _reserved_bit_28: bool = false, + _reserved_bit_29: bool = false, + _reserved_bit_30: bool = false, + _reserved_bit_31: bool = false, +}; +pub const SourceLanguage = extern enum(u32) { + Unknown = 0, + ESSL = 1, + GLSL = 2, + OpenCL_C = 3, + OpenCL_CPP = 4, + HLSL = 5, + _, +}; +pub const ExecutionModel = extern enum(u32) { + Vertex = 0, + TessellationControl = 1, + TessellationEvaluation = 2, + Geometry = 3, + Fragment = 4, + GLCompute = 5, + Kernel = 6, + TaskNV = 5267, + MeshNV = 5268, + RayGenerationNV = 5313, + RayGenerationKHR = 5313, + IntersectionNV = 5314, + IntersectionKHR = 5314, + AnyHitNV = 5315, + AnyHitKHR = 5315, + ClosestHitNV = 5316, + ClosestHitKHR = 5316, + MissNV = 5317, + MissKHR = 5317, + CallableNV = 5318, + CallableKHR = 5318, + _, +}; +pub const AddressingModel = extern enum(u32) { + Logical = 0, + Physical32 = 1, + Physical64 = 2, + PhysicalStorageBuffer64 = 5348, + PhysicalStorageBuffer64EXT = 5348, + _, +}; +pub const MemoryModel = extern enum(u32) { + Simple = 0, + GLSL450 = 1, + OpenCL = 2, + Vulkan = 3, + VulkanKHR = 3, + _, +}; +pub const ExecutionMode = extern enum(u32) { + Invocations = 0, + SpacingEqual = 1, + SpacingFractionalEven = 2, + SpacingFractionalOdd = 3, + VertexOrderCw = 4, + VertexOrderCcw = 5, + PixelCenterInteger = 6, + OriginUpperLeft = 7, + OriginLowerLeft = 8, + EarlyFragmentTests = 9, + PointMode = 10, + Xfb = 11, + DepthReplacing = 12, + DepthGreater = 14, + DepthLess = 15, + DepthUnchanged = 16, + LocalSize = 17, + LocalSizeHint = 18, + InputPoints = 19, + InputLines = 20, + InputLinesAdjacency = 21, + Triangles = 22, + InputTrianglesAdjacency = 23, + Quads = 24, + Isolines = 25, + OutputVertices = 26, + OutputPoints = 27, + OutputLineStrip = 28, + OutputTriangleStrip = 29, + VecTypeHint = 30, + ContractionOff = 31, + Initializer = 33, + Finalizer = 34, + SubgroupSize = 35, + SubgroupsPerWorkgroup = 36, + SubgroupsPerWorkgroupId = 37, + LocalSizeId = 38, + LocalSizeHintId = 39, + PostDepthCoverage = 4446, + DenormPreserve = 4459, + DenormFlushToZero = 4460, + SignedZeroInfNanPreserve = 4461, + RoundingModeRTE = 4462, + RoundingModeRTZ = 4463, + StencilRefReplacingEXT = 5027, + OutputLinesNV = 5269, + OutputPrimitivesNV = 5270, + DerivativeGroupQuadsNV = 5289, + DerivativeGroupLinearNV = 5290, + OutputTrianglesNV = 5298, + PixelInterlockOrderedEXT = 5366, + PixelInterlockUnorderedEXT = 5367, + SampleInterlockOrderedEXT = 5368, + SampleInterlockUnorderedEXT = 5369, + ShadingRateInterlockOrderedEXT = 5370, + ShadingRateInterlockUnorderedEXT = 5371, + MaxWorkgroupSizeINTEL = 5893, + MaxWorkDimINTEL = 5894, + NoGlobalOffsetINTEL = 5895, + NumSIMDWorkitemsINTEL = 5896, + _, +}; +pub const StorageClass = extern enum(u32) { + UniformConstant = 0, + Input = 1, + Uniform = 2, + Output = 3, + Workgroup = 4, + CrossWorkgroup = 5, + Private = 6, + Function = 7, + Generic = 8, + PushConstant = 9, + AtomicCounter = 10, + Image = 11, + StorageBuffer = 12, + CallableDataNV = 5328, + CallableDataKHR = 5328, + IncomingCallableDataNV = 5329, + IncomingCallableDataKHR = 5329, + RayPayloadNV = 5338, + RayPayloadKHR = 5338, + HitAttributeNV = 5339, + HitAttributeKHR = 5339, + IncomingRayPayloadNV = 5342, + IncomingRayPayloadKHR = 5342, + ShaderRecordBufferNV = 5343, + ShaderRecordBufferKHR = 5343, + PhysicalStorageBuffer = 5349, + PhysicalStorageBufferEXT = 5349, + CodeSectionINTEL = 5605, + _, +}; +pub const Dim = extern enum(u32) { + @"1D" = 0, + @"2D" = 1, + @"3D" = 2, + Cube = 3, + Rect = 4, + Buffer = 5, + SubpassData = 6, + _, +}; +pub const SamplerAddressingMode = extern enum(u32) { + None = 0, + ClampToEdge = 1, + Clamp = 2, + Repeat = 3, + RepeatMirrored = 4, + _, +}; +pub const SamplerFilterMode = extern enum(u32) { + Nearest = 0, + Linear = 1, + _, +}; +pub const ImageFormat = extern enum(u32) { + Unknown = 0, + Rgba32f = 1, + Rgba16f = 2, + R32f = 3, + Rgba8 = 4, + Rgba8Snorm = 5, + Rg32f = 6, + Rg16f = 7, + R11fG11fB10f = 8, + R16f = 9, + Rgba16 = 10, + Rgb10A2 = 11, + Rg16 = 12, + Rg8 = 13, + R16 = 14, + R8 = 15, + Rgba16Snorm = 16, + Rg16Snorm = 17, + Rg8Snorm = 18, + R16Snorm = 19, + R8Snorm = 20, + Rgba32i = 21, + Rgba16i = 22, + Rgba8i = 23, + R32i = 24, + Rg32i = 25, + Rg16i = 26, + Rg8i = 27, + R16i = 28, + R8i = 29, + Rgba32ui = 30, + Rgba16ui = 31, + Rgba8ui = 32, + R32ui = 33, + Rgb10a2ui = 34, + Rg32ui = 35, + Rg16ui = 36, + Rg8ui = 37, + R16ui = 38, + R8ui = 39, + R64ui = 40, + R64i = 41, + _, +}; +pub const ImageChannelOrder = extern enum(u32) { + R = 0, + A = 1, + RG = 2, + RA = 3, + RGB = 4, + RGBA = 5, + BGRA = 6, + ARGB = 7, + Intensity = 8, + Luminance = 9, + Rx = 10, + RGx = 11, + RGBx = 12, + Depth = 13, + DepthStencil = 14, + sRGB = 15, + sRGBx = 16, + sRGBA = 17, + sBGRA = 18, + ABGR = 19, + _, +}; +pub const ImageChannelDataType = extern enum(u32) { + SnormInt8 = 0, + SnormInt16 = 1, + UnormInt8 = 2, + UnormInt16 = 3, + UnormShort565 = 4, + UnormShort555 = 5, + UnormInt101010 = 6, + SignedInt8 = 7, + SignedInt16 = 8, + SignedInt32 = 9, + UnsignedInt8 = 10, + UnsignedInt16 = 11, + UnsignedInt32 = 12, + HalfFloat = 13, + Float = 14, + UnormInt24 = 15, + UnormInt101010_2 = 16, + _, +}; +pub const FPRoundingMode = extern enum(u32) { + RTE = 0, + RTZ = 1, + RTP = 2, + RTN = 3, + _, +}; +pub const LinkageType = extern enum(u32) { + Export = 0, + Import = 1, + _, +}; +pub const AccessQualifier = extern enum(u32) { + ReadOnly = 0, + WriteOnly = 1, + ReadWrite = 2, + _, +}; +pub const FunctionParameterAttribute = extern enum(u32) { + Zext = 0, + Sext = 1, + ByVal = 2, + Sret = 3, + NoAlias = 4, + NoCapture = 5, + NoWrite = 6, + NoReadWrite = 7, + _, +}; +pub const Decoration = extern enum(u32) { + RelaxedPrecision = 0, + SpecId = 1, + Block = 2, + BufferBlock = 3, + RowMajor = 4, + ColMajor = 5, + ArrayStride = 6, + MatrixStride = 7, + GLSLShared = 8, + GLSLPacked = 9, + CPacked = 10, + BuiltIn = 11, + NoPerspective = 13, + Flat = 14, + Patch = 15, + Centroid = 16, + Sample = 17, + Invariant = 18, + Restrict = 19, + Aliased = 20, + Volatile = 21, + Constant = 22, + Coherent = 23, + NonWritable = 24, + NonReadable = 25, + Uniform = 26, + UniformId = 27, + SaturatedConversion = 28, + Stream = 29, + Location = 30, + Component = 31, + Index = 32, + Binding = 33, + DescriptorSet = 34, + Offset = 35, + XfbBuffer = 36, + XfbStride = 37, + FuncParamAttr = 38, + FPRoundingMode = 39, + FPFastMathMode = 40, + LinkageAttributes = 41, + NoContraction = 42, + InputAttachmentIndex = 43, + Alignment = 44, + MaxByteOffset = 45, + AlignmentId = 46, + MaxByteOffsetId = 47, + NoSignedWrap = 4469, + NoUnsignedWrap = 4470, + ExplicitInterpAMD = 4999, + OverrideCoverageNV = 5248, + PassthroughNV = 5250, + ViewportRelativeNV = 5252, + SecondaryViewportRelativeNV = 5256, + PerPrimitiveNV = 5271, + PerViewNV = 5272, + PerTaskNV = 5273, + PerVertexNV = 5285, + NonUniform = 5300, + NonUniformEXT = 5300, + RestrictPointer = 5355, + RestrictPointerEXT = 5355, + AliasedPointer = 5356, + AliasedPointerEXT = 5356, + ReferencedIndirectlyINTEL = 5602, + CounterBuffer = 5634, + HlslCounterBufferGOOGLE = 5634, + UserSemantic = 5635, + HlslSemanticGOOGLE = 5635, + UserTypeGOOGLE = 5636, + RegisterINTEL = 5825, + MemoryINTEL = 5826, + NumbanksINTEL = 5827, + BankwidthINTEL = 5828, + MaxPrivateCopiesINTEL = 5829, + SinglepumpINTEL = 5830, + DoublepumpINTEL = 5831, + MaxReplicatesINTEL = 5832, + SimpleDualPortINTEL = 5833, + MergeINTEL = 5834, + BankBitsINTEL = 5835, + ForcePow2DepthINTEL = 5836, + _, +}; +pub const BuiltIn = extern enum(u32) { + Position = 0, + PointSize = 1, + ClipDistance = 3, + CullDistance = 4, + VertexId = 5, + InstanceId = 6, + PrimitiveId = 7, + InvocationId = 8, + Layer = 9, + ViewportIndex = 10, + TessLevelOuter = 11, + TessLevelInner = 12, + TessCoord = 13, + PatchVertices = 14, + FragCoord = 15, + PointCoord = 16, + FrontFacing = 17, + SampleId = 18, + SamplePosition = 19, + SampleMask = 20, + FragDepth = 22, + HelperInvocation = 23, + NumWorkgroups = 24, + WorkgroupSize = 25, + WorkgroupId = 26, + LocalInvocationId = 27, + GlobalInvocationId = 28, + LocalInvocationIndex = 29, + WorkDim = 30, + GlobalSize = 31, + EnqueuedWorkgroupSize = 32, + GlobalOffset = 33, + GlobalLinearId = 34, + SubgroupSize = 36, + SubgroupMaxSize = 37, + NumSubgroups = 38, + NumEnqueuedSubgroups = 39, + SubgroupId = 40, + SubgroupLocalInvocationId = 41, + VertexIndex = 42, + InstanceIndex = 43, + SubgroupEqMask = 4416, + SubgroupGeMask = 4417, + SubgroupGtMask = 4418, + SubgroupLeMask = 4419, + SubgroupLtMask = 4420, + SubgroupEqMaskKHR = 4416, + SubgroupGeMaskKHR = 4417, + SubgroupGtMaskKHR = 4418, + SubgroupLeMaskKHR = 4419, + SubgroupLtMaskKHR = 4420, + BaseVertex = 4424, + BaseInstance = 4425, + DrawIndex = 4426, + PrimitiveShadingRateKHR = 4432, + DeviceIndex = 4438, + ViewIndex = 4440, + ShadingRateKHR = 4444, + BaryCoordNoPerspAMD = 4992, + BaryCoordNoPerspCentroidAMD = 4993, + BaryCoordNoPerspSampleAMD = 4994, + BaryCoordSmoothAMD = 4995, + BaryCoordSmoothCentroidAMD = 4996, + BaryCoordSmoothSampleAMD = 4997, + BaryCoordPullModelAMD = 4998, + FragStencilRefEXT = 5014, + ViewportMaskNV = 5253, + SecondaryPositionNV = 5257, + SecondaryViewportMaskNV = 5258, + PositionPerViewNV = 5261, + ViewportMaskPerViewNV = 5262, + FullyCoveredEXT = 5264, + TaskCountNV = 5274, + PrimitiveCountNV = 5275, + PrimitiveIndicesNV = 5276, + ClipDistancePerViewNV = 5277, + CullDistancePerViewNV = 5278, + LayerPerViewNV = 5279, + MeshViewCountNV = 5280, + MeshViewIndicesNV = 5281, + BaryCoordNV = 5286, + BaryCoordNoPerspNV = 5287, + FragSizeEXT = 5292, + FragmentSizeNV = 5292, + FragInvocationCountEXT = 5293, + InvocationsPerPixelNV = 5293, + LaunchIdNV = 5319, + LaunchIdKHR = 5319, + LaunchSizeNV = 5320, + LaunchSizeKHR = 5320, + WorldRayOriginNV = 5321, + WorldRayOriginKHR = 5321, + WorldRayDirectionNV = 5322, + WorldRayDirectionKHR = 5322, + ObjectRayOriginNV = 5323, + ObjectRayOriginKHR = 5323, + ObjectRayDirectionNV = 5324, + ObjectRayDirectionKHR = 5324, + RayTminNV = 5325, + RayTminKHR = 5325, + RayTmaxNV = 5326, + RayTmaxKHR = 5326, + InstanceCustomIndexNV = 5327, + InstanceCustomIndexKHR = 5327, + ObjectToWorldNV = 5330, + ObjectToWorldKHR = 5330, + WorldToObjectNV = 5331, + WorldToObjectKHR = 5331, + HitTNV = 5332, + HitKindNV = 5333, + HitKindKHR = 5333, + IncomingRayFlagsNV = 5351, + IncomingRayFlagsKHR = 5351, + RayGeometryIndexKHR = 5352, + WarpsPerSMNV = 5374, + SMCountNV = 5375, + WarpIDNV = 5376, + SMIDNV = 5377, + _, +}; +pub const Scope = extern enum(u32) { + CrossDevice = 0, + Device = 1, + Workgroup = 2, + Subgroup = 3, + Invocation = 4, + QueueFamily = 5, + QueueFamilyKHR = 5, + ShaderCallKHR = 6, + _, +}; +pub const GroupOperation = extern enum(u32) { + Reduce = 0, + InclusiveScan = 1, + ExclusiveScan = 2, + ClusteredReduce = 3, + PartitionedReduceNV = 6, + PartitionedInclusiveScanNV = 7, + PartitionedExclusiveScanNV = 8, + _, +}; +pub const KernelEnqueueFlags = extern enum(u32) { + NoWait = 0, + WaitKernel = 1, + WaitWorkGroup = 2, + _, +}; +pub const Capability = extern enum(u32) { + Matrix = 0, + Shader = 1, + Geometry = 2, + Tessellation = 3, + Addresses = 4, + Linkage = 5, + Kernel = 6, + Vector16 = 7, + Float16Buffer = 8, + Float16 = 9, + Float64 = 10, + Int64 = 11, + Int64Atomics = 12, + ImageBasic = 13, + ImageReadWrite = 14, + ImageMipmap = 15, + Pipes = 17, + Groups = 18, + DeviceEnqueue = 19, + LiteralSampler = 20, + AtomicStorage = 21, + Int16 = 22, + TessellationPointSize = 23, + GeometryPointSize = 24, + ImageGatherExtended = 25, + StorageImageMultisample = 27, + UniformBufferArrayDynamicIndexing = 28, + SampledImageArrayDynamicIndexing = 29, + StorageBufferArrayDynamicIndexing = 30, + StorageImageArrayDynamicIndexing = 31, + ClipDistance = 32, + CullDistance = 33, + ImageCubeArray = 34, + SampleRateShading = 35, + ImageRect = 36, + SampledRect = 37, + GenericPointer = 38, + Int8 = 39, + InputAttachment = 40, + SparseResidency = 41, + MinLod = 42, + Sampled1D = 43, + Image1D = 44, + SampledCubeArray = 45, + SampledBuffer = 46, + ImageBuffer = 47, + ImageMSArray = 48, + StorageImageExtendedFormats = 49, + ImageQuery = 50, + DerivativeControl = 51, + InterpolationFunction = 52, + TransformFeedback = 53, + GeometryStreams = 54, + StorageImageReadWithoutFormat = 55, + StorageImageWriteWithoutFormat = 56, + MultiViewport = 57, + SubgroupDispatch = 58, + NamedBarrier = 59, + PipeStorage = 60, + GroupNonUniform = 61, + GroupNonUniformVote = 62, + GroupNonUniformArithmetic = 63, + GroupNonUniformBallot = 64, + GroupNonUniformShuffle = 65, + GroupNonUniformShuffleRelative = 66, + GroupNonUniformClustered = 67, + GroupNonUniformQuad = 68, + ShaderLayer = 69, + ShaderViewportIndex = 70, + FragmentShadingRateKHR = 4422, + SubgroupBallotKHR = 4423, + DrawParameters = 4427, + SubgroupVoteKHR = 4431, + StorageBuffer16BitAccess = 4433, + StorageUniformBufferBlock16 = 4433, + UniformAndStorageBuffer16BitAccess = 4434, + StorageUniform16 = 4434, + StoragePushConstant16 = 4435, + StorageInputOutput16 = 4436, + DeviceGroup = 4437, + MultiView = 4439, + VariablePointersStorageBuffer = 4441, + VariablePointers = 4442, + AtomicStorageOps = 4445, + SampleMaskPostDepthCoverage = 4447, + StorageBuffer8BitAccess = 4448, + UniformAndStorageBuffer8BitAccess = 4449, + StoragePushConstant8 = 4450, + DenormPreserve = 4464, + DenormFlushToZero = 4465, + SignedZeroInfNanPreserve = 4466, + RoundingModeRTE = 4467, + RoundingModeRTZ = 4468, + RayQueryProvisionalKHR = 4471, + RayQueryKHR = 4472, + RayTraversalPrimitiveCullingKHR = 4478, + RayTracingKHR = 4479, + Float16ImageAMD = 5008, + ImageGatherBiasLodAMD = 5009, + FragmentMaskAMD = 5010, + StencilExportEXT = 5013, + ImageReadWriteLodAMD = 5015, + Int64ImageEXT = 5016, + ShaderClockKHR = 5055, + SampleMaskOverrideCoverageNV = 5249, + GeometryShaderPassthroughNV = 5251, + ShaderViewportIndexLayerEXT = 5254, + ShaderViewportIndexLayerNV = 5254, + ShaderViewportMaskNV = 5255, + ShaderStereoViewNV = 5259, + PerViewAttributesNV = 5260, + FragmentFullyCoveredEXT = 5265, + MeshShadingNV = 5266, + ImageFootprintNV = 5282, + FragmentBarycentricNV = 5284, + ComputeDerivativeGroupQuadsNV = 5288, + FragmentDensityEXT = 5291, + ShadingRateNV = 5291, + GroupNonUniformPartitionedNV = 5297, + ShaderNonUniform = 5301, + ShaderNonUniformEXT = 5301, + RuntimeDescriptorArray = 5302, + RuntimeDescriptorArrayEXT = 5302, + InputAttachmentArrayDynamicIndexing = 5303, + InputAttachmentArrayDynamicIndexingEXT = 5303, + UniformTexelBufferArrayDynamicIndexing = 5304, + UniformTexelBufferArrayDynamicIndexingEXT = 5304, + StorageTexelBufferArrayDynamicIndexing = 5305, + StorageTexelBufferArrayDynamicIndexingEXT = 5305, + UniformBufferArrayNonUniformIndexing = 5306, + UniformBufferArrayNonUniformIndexingEXT = 5306, + SampledImageArrayNonUniformIndexing = 5307, + SampledImageArrayNonUniformIndexingEXT = 5307, + StorageBufferArrayNonUniformIndexing = 5308, + StorageBufferArrayNonUniformIndexingEXT = 5308, + StorageImageArrayNonUniformIndexing = 5309, + StorageImageArrayNonUniformIndexingEXT = 5309, + InputAttachmentArrayNonUniformIndexing = 5310, + InputAttachmentArrayNonUniformIndexingEXT = 5310, + UniformTexelBufferArrayNonUniformIndexing = 5311, + UniformTexelBufferArrayNonUniformIndexingEXT = 5311, + StorageTexelBufferArrayNonUniformIndexing = 5312, + StorageTexelBufferArrayNonUniformIndexingEXT = 5312, + RayTracingNV = 5340, + VulkanMemoryModel = 5345, + VulkanMemoryModelKHR = 5345, + VulkanMemoryModelDeviceScope = 5346, + VulkanMemoryModelDeviceScopeKHR = 5346, + PhysicalStorageBufferAddresses = 5347, + PhysicalStorageBufferAddressesEXT = 5347, + ComputeDerivativeGroupLinearNV = 5350, + RayTracingProvisionalKHR = 5353, + CooperativeMatrixNV = 5357, + FragmentShaderSampleInterlockEXT = 5363, + FragmentShaderShadingRateInterlockEXT = 5372, + ShaderSMBuiltinsNV = 5373, + FragmentShaderPixelInterlockEXT = 5378, + DemoteToHelperInvocationEXT = 5379, + SubgroupShuffleINTEL = 5568, + SubgroupBufferBlockIOINTEL = 5569, + SubgroupImageBlockIOINTEL = 5570, + SubgroupImageMediaBlockIOINTEL = 5579, + IntegerFunctions2INTEL = 5584, + FunctionPointersINTEL = 5603, + IndirectReferencesINTEL = 5604, + SubgroupAvcMotionEstimationINTEL = 5696, + SubgroupAvcMotionEstimationIntraINTEL = 5697, + SubgroupAvcMotionEstimationChromaINTEL = 5698, + FPGAMemoryAttributesINTEL = 5824, + UnstructuredLoopControlsINTEL = 5886, + FPGALoopControlsINTEL = 5888, + KernelAttributesINTEL = 5892, + FPGAKernelAttributesINTEL = 5897, + BlockingPipesINTEL = 5945, + FPGARegINTEL = 5948, + AtomicFloat32AddEXT = 6033, + AtomicFloat64AddEXT = 6034, + _, +}; +pub const RayQueryIntersection = extern enum(u32) { + RayQueryCandidateIntersectionKHR = 0, + RayQueryCommittedIntersectionKHR = 1, + _, +}; +pub const RayQueryCommittedIntersectionType = extern enum(u32) { + RayQueryCommittedIntersectionNoneKHR = 0, + RayQueryCommittedIntersectionTriangleKHR = 1, + RayQueryCommittedIntersectionGeneratedKHR = 2, + _, +}; +pub const RayQueryCandidateIntersectionType = extern enum(u32) { + RayQueryCandidateIntersectionTriangleKHR = 0, + RayQueryCandidateIntersectionAABBKHR = 1, + _, +}; -- cgit v1.2.3 From b2b87b590011d8df52874e3f9bd1f88d1b0189d1 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 19 Jan 2021 00:34:44 +0100 Subject: SPIR-V: Linking and codegen setup --- src/Module.zig | 5 +- src/codegen/spirv.zig | 22 +++++++++ src/link.zig | 29 +++++++---- src/link/SpirV.zig | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 3 ++ 5 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 src/codegen/spirv.zig create mode 100644 src/link/SpirV.zig (limited to 'src/codegen') diff --git a/src/Module.zig b/src/Module.zig index fa9722814e..464124c7b9 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1622,7 +1622,7 @@ pub fn analyzeContainer(self: *Module, container_scope: *Scope.Container) !void // in `Decl` to notice that the line number did not change. self.comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl }); }, - .c, .wasm => {}, + .c, .wasm, .spirv => {}, } } } else { @@ -1855,6 +1855,7 @@ fn allocateNewDecl( .macho => .{ .macho = link.File.MachO.TextBlock.empty }, .c => .{ .c = link.File.C.DeclBlock.empty }, .wasm => .{ .wasm = {} }, + .spirv => .{ .spirv = {} }, }, .fn_link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = {} }, @@ -1862,6 +1863,7 @@ fn allocateNewDecl( .macho => .{ .macho = link.File.MachO.SrcFn.empty }, .c => .{ .c = link.File.C.FnBlock.empty }, .wasm => .{ .wasm = null }, + .spirv => .{ .spirv = .{} }, }, .generation = 0, .is_pub = false, @@ -1959,6 +1961,7 @@ pub fn analyzeExport( .macho => .{ .macho = link.File.MachO.Export{} }, .c => .{ .c = {} }, .wasm => .{ .wasm = {} }, + .spirv => .{ .spirv = {} }, }, .owner_decl = owner_decl, .exported_decl = exported_decl, diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig new file mode 100644 index 0000000000..f0ebd49e1d --- /dev/null +++ b/src/codegen/spirv.zig @@ -0,0 +1,22 @@ +const std = @import("std"); +const spec = @import("spirv/spec.zig"); +const Module = @import("../Module.zig"); +const Decl = Module.Decl; + +pub const SPIRVModule = struct { + // TODO: Also use a free list. + next_id: u32 = 0, + + pub fn allocId(self: *SPIRVModule) u32 { + defer self.next_id += 1; + return self.next_id; + } + + pub fn idBound(self: *SPIRVModule) u32 { + return self.next_id; + } + + pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void { + + } +}; diff --git a/src/link.zig b/src/link.zig index fdbb7efd4b..a19a261f55 100644 --- a/src/link.zig +++ b/src/link.zig @@ -142,7 +142,7 @@ pub const File = struct { macho: MachO.SrcFn, c: C.FnBlock, wasm: ?Wasm.FnData, - spirv: void, + spirv: SpirV.FnData, }; pub const Export = union { @@ -180,7 +180,7 @@ pub const File = struct { .macho => &(try MachO.createEmpty(allocator, options)).base, .wasm => &(try Wasm.createEmpty(allocator, options)).base, .c => unreachable, // Reported error earlier. - .spirv => return error.SpirVObjectFormatUnimplemented, + .spirv => &(try SpirV.createEmpty(allocator, options)).base, .hex => return error.HexObjectFormatUnimplemented, .raw => return error.RawObjectFormatUnimplemented, }; @@ -196,7 +196,7 @@ pub const File = struct { .macho => &(try MachO.createEmpty(allocator, options)).base, .wasm => &(try Wasm.createEmpty(allocator, options)).base, .c => unreachable, // Reported error earlier. - .spirv => return error.SpirVObjectFormatUnimplemented, + .spirv => &(try SpirV.createEmpty(allocator, options)).base, .hex => return error.HexObjectFormatUnimplemented, .raw => return error.RawObjectFormatUnimplemented, }; @@ -212,7 +212,7 @@ pub const File = struct { .macho => &(try MachO.openPath(allocator, sub_path, options)).base, .wasm => &(try Wasm.openPath(allocator, sub_path, options)).base, .c => &(try C.openPath(allocator, sub_path, options)).base, - .spirv => return error.SpirVObjectFormatUnimplemented, + .spirv => &(try SpirV.openPath(allocator, sub_path, options)).base, .hex => return error.HexObjectFormatUnimplemented, .raw => return error.RawObjectFormatUnimplemented, }; @@ -242,7 +242,7 @@ pub const File = struct { .mode = determineMode(base.options), }); }, - .c, .wasm => {}, + .c, .wasm, .spirv => {}, } } @@ -287,7 +287,7 @@ pub const File = struct { f.close(); base.file = null; }, - .c, .wasm => {}, + .c, .wasm, .spirv => {}, } } @@ -300,6 +300,7 @@ pub const File = struct { .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl), .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl), + .spirv => return @fieldParentPtr(SpirV, "base", base).updateDecl(module, decl), } } @@ -309,7 +310,7 @@ pub const File = struct { .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl), .c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl), - .wasm => {}, + .wasm, .spirv => {}, } } @@ -321,7 +322,7 @@ pub const File = struct { .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl), .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl), - .wasm => {}, + .wasm, .spirv => {}, } } @@ -368,6 +369,11 @@ pub const File = struct { parent.deinit(); base.allocator.destroy(parent); }, + .spirv => { + const parent = @fieldParentPtr(SpirV, "base", base); + parent.deinit(); + base.allocator.destroy(parent); + }, } } @@ -401,6 +407,7 @@ pub const File = struct { .macho => return @fieldParentPtr(MachO, "base", base).flush(comp), .c => return @fieldParentPtr(C, "base", base).flush(comp), .wasm => return @fieldParentPtr(Wasm, "base", base).flush(comp), + .spirv => return @fieldParentPtr(SpirV, "base", base).flush(comp), } } @@ -413,6 +420,7 @@ pub const File = struct { .macho => return @fieldParentPtr(MachO, "base", base).flushModule(comp), .c => return @fieldParentPtr(C, "base", base).flushModule(comp), .wasm => return @fieldParentPtr(Wasm, "base", base).flushModule(comp), + .spirv => return @fieldParentPtr(SpirV, "base", base).flushModule(comp), } } @@ -424,6 +432,7 @@ pub const File = struct { .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), .c => @fieldParentPtr(C, "base", base).freeDecl(decl), .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl), + .spirv => @fieldParentPtr(SpirV, "base", base).freeDecl(decl), } } @@ -433,7 +442,7 @@ pub const File = struct { .elf => return @fieldParentPtr(Elf, "base", base).error_flags, .macho => return @fieldParentPtr(MachO, "base", base).error_flags, .c => return .{ .no_entry_point_found = false }, - .wasm => return ErrorFlags{}, + .wasm, .spirv => return ErrorFlags{}, } } @@ -451,6 +460,7 @@ pub const File = struct { .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports), .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports), .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports), + .spirv => return @fieldParentPtr(SpirV, "base", base).updateDeclExports(module, decl, exports), } } @@ -461,6 +471,7 @@ pub const File = struct { .macho => return @fieldParentPtr(MachO, "base", base).getDeclVAddr(decl), .c => unreachable, .wasm => unreachable, + .spirv => unreachable, } } diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig new file mode 100644 index 0000000000..207e460174 --- /dev/null +++ b/src/link/SpirV.zig @@ -0,0 +1,131 @@ +const SpirV = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); +const link = @import("../link.zig"); +const codegen = @import("../codegen/spirv.zig"); +const trace = @import("../tracy.zig").trace; +const build_options = @import("build_options"); +const spec = @import("../codegen/spirv/spec.zig"); + +pub const FnData = struct { + id: ?u32 = null, + code: std.ArrayListUnmanaged(u32) = .{}, +}; + +base: link.File, + +// TODO: Does this file need to support multiple independent modules? +spirv_module: codegen.SPIRVModule = .{}, + +pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV { + const spirv = try gpa.create(SpirV); + spirv.* = .{ + .base = .{ + .tag = .spirv, + .options = options, + .file = null, + .allocator = gpa, + }, + }; + return spirv; +} + +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*SpirV { + assert(options.object_format == .spirv); + + if (options.use_llvm) return error.LLVM_BackendIsTODO_ForSpirV; // TODO: LLVM Doesn't support SpirV at all. + if (options.use_lld) return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all. + + // TODO: read the file and keep vaild parts instead of truncating + const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true }); + errdefer file.close(); + + const spirv = try createEmpty(allocator, options); + errdefer spirv.base.destroy(); + + spirv.base.file = file; + return spirv; +} + +pub fn deinit(self: *SpirV) void { +} + +pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const fn_data = &decl.fn_link.spirv; + if (fn_data.id == null) { + fn_data.id = self.spirv_module.allocId(); + } + + var managed_code = fn_data.code.toManaged(self.base.allocator); + managed_code.items.len = 0; + + try self.spirv_module.genDecl(fn_data.id.?, &managed_code, decl); + fn_data.code = managed_code.toUnmanaged(); + + // Free excess allocated memory for this Decl. + fn_data.code.shrinkAndFree(self.base.allocator, fn_data.code.items.len); +} + +pub fn updateDeclExports( + self: *SpirV, + module: *Module, + decl: *const Module.Decl, + exports: []const *Module.Export, +) !void {} + +pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void { + decl.fn_link.spirv.code.deinit(self.base.allocator); + decl.fn_link.spirv = undefined; +} + +pub fn flush(self: *SpirV, comp: *Compilation) !void { + if (build_options.have_llvm and self.base.options.use_lld) { + return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all. + } else { + return self.flushModule(comp); + } +} + +pub fn flushModule(self: *SpirV, comp: *Compilation) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const module = self.base.options.module.?; + + const file = self.base.file.?; + var bw = std.io.bufferedWriter(file.writer()); + const writer = bw.writer(); + + // Header + // SPIR-V files support both little and big endian words. The actual format is disambiguated by + // the magic number. This backend uses little endian. + try writer.writeIntLittle(u32, spec.magic_number); + try writer.writeIntLittle(u32, (spec.version.major << 16) | (spec.version.minor) << 8); + try writer.writeIntLittle(u32, 0); // TODO: Register Zig compiler magic number. + try writer.writeIntLittle(u32, self.spirv_module.idBound()); + try writer.writeIntLittle(u32, 0); // Schema. + + // Declarations + for (module.decl_table.items()) |entry| { + const decl = entry.value; + switch (decl.typed_value) { + .most_recent => |tvm| { + const fn_data = &decl.fn_link.spirv; + for (fn_data.code.items) |word| { + try writer.writeIntLittle(u32, word); + } + }, + .never_succeeded => continue, + } + } + + try bw.flush(); +} diff --git a/src/main.zig b/src/main.zig index 13bea13a5e..a86ec4ccf5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -302,6 +302,7 @@ const usage_build_generic = \\ pe Portable Executable (Windows) \\ coff Common Object File Format (Windows) \\ macho macOS relocatables + \\ spirv Standard, Portable Intermediate Representation V (SPIR-V) \\ hex (planned) Intel IHEX \\ raw (planned) Dump machine code directly \\ -dirafter [dir] Add directory to AFTER include search path @@ -1515,6 +1516,8 @@ fn buildOutputType( break :blk .hex; } else if (mem.eql(u8, ofmt, "raw")) { break :blk .raw; + } else if (mem.eql(u8, ofmt, "spirv")) { + break :blk .spirv; } else { fatal("unsupported object format: {s}", .{ofmt}); } -- cgit v1.2.3 From 02c138fe7011346ebab5e4b24ba0f8575bb52173 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 18 Jan 2021 23:47:25 +0100 Subject: SPIR-V: Add glsl450 and vulkan spir-v operating system definitions --- lib/std/target.zig | 18 +++++++++++++++--- lib/std/zig/cross_target.zig | 4 ++++ src/codegen/llvm.zig | 5 +++++ src/link/SpirV.zig | 20 ++++++++++++++++++++ src/target.zig | 4 ++-- src/type.zig | 2 ++ 6 files changed, 48 insertions(+), 5 deletions(-) (limited to 'src/codegen') diff --git a/lib/std/target.zig b/lib/std/target.zig index b3e0f8afdd..66b5f560c1 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -57,7 +57,9 @@ pub const Target = struct { wasi, emscripten, uefi, - opencl, // SPIR-V on OpenCL + opencl, + glsl450, + vulkan, other, pub fn isDarwin(tag: Tag) bool { @@ -249,7 +251,9 @@ pub const Target = struct { .wasi, .emscripten, .uefi, - .opencl, + .opencl, // TODO: OpenCL versions + .glsl450, // TODO: GLSL versions + .vulkan, .other, => return .{ .none = {} }, @@ -406,6 +410,8 @@ pub const Target = struct { .emscripten, .uefi, .opencl, + .glsl450, + .vulkan, .other, => false, }; @@ -497,7 +503,9 @@ pub const Target = struct { .wasi, .emscripten, => return .musl, - .opencl, // TODO: Where should this go? + .opencl, // TODO: SPIR-V ABIs with Linkage capability + .glsl450, + .vulkan, => return .none, } } @@ -1367,6 +1375,8 @@ pub const Target = struct { .windows, .emscripten, .opencl, + .glsl450, + .vulkan, .other, => return false, else => return true, @@ -1547,6 +1557,8 @@ pub const Target = struct { .emscripten, .wasi, .opencl, + .glsl450, + .vulkan, .other, => return result, diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index 1e9066b90b..c34dcc2bd3 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -131,6 +131,8 @@ pub const CrossTarget = struct { .emscripten, .uefi, .opencl, + .glsl450, + .vulkan, .other, => { self.os_version_min = .{ .none = {} }; @@ -732,6 +734,8 @@ pub const CrossTarget = struct { .emscripten, .uefi, .opencl, + .glsl450, + .vulkan, .other, => return error.InvalidOperatingSystemVersion, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 1edd466d54..df6a58b1e2 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -69,6 +69,8 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { .renderscript64 => "renderscript64", .ve => "ve", .spu_2 => return error.LLVMBackendDoesNotSupportSPUMarkII, + .spirv32 => return error.LLVMBackendDoesNotSupportSPIRV, + .spirv64 => return error.LLVMBackendDoesNotSupportSPIRV, }; // TODO Add a sub-arch for some architectures depending on CPU features. @@ -109,6 +111,9 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![:0]u8 { .wasi => "wasi", .emscripten => "emscripten", .uefi => "windows", + .opencl => return error.LLVMBackendDoesNotSupportOpenCL, + .glsl450 => return error.LLVMBackendDoesNotSupportGLSL450, + .vulkan => return error.LLVMBackendDoesNotSupportVulkan, .other => "unknown", }; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 207e460174..68edfab845 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -12,6 +12,8 @@ const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); const spec = @import("../codegen/spirv/spec.zig"); +//! SPIR-V Documentation: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html + pub const FnData = struct { id: ?u32 = null, code: std.ArrayListUnmanaged(u32) = .{}, @@ -32,6 +34,22 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV { .allocator = gpa, }, }; + + // TODO: Figure out where to put all of these + switch (options.target.cpu.arch) { + .spirv32, .spirv64 => {}, + else => return error.TODOArchNotSupported, + } + + switch (options.target.os.tag) { + .opencl, .glsl450, .vulkan => {}, + else => return error.TODOOsNotSupported, + } + + if (options.target.abi != .none) { + return error.TODOAbiNotSupported; + } + return spirv; } @@ -119,6 +137,8 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void { switch (decl.typed_value) { .most_recent => |tvm| { const fn_data = &decl.fn_link.spirv; + + // TODO: This could probably be more efficient. for (fn_data.code.items) |word| { try writer.writeIntLittle(u32, word); } diff --git a/src/target.zig b/src/target.zig index c3df682ce0..e167520f89 100644 --- a/src/target.zig +++ b/src/target.zig @@ -189,7 +189,7 @@ pub fn supportsStackProbing(target: std.Target) bool { pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType { return switch (os_tag) { - .freestanding, .other => .UnknownOS, + .freestanding, .other, .opencl, .glsl450, .vulkan => .UnknownOS, .windows, .uefi => .Win32, .ananas => .Ananas, .cloudabi => .CloudABI, @@ -280,7 +280,7 @@ pub fn archToLLVM(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { .renderscript32 => .renderscript32, .renderscript64 => .renderscript64, .ve => .ve, - .spu_2 => .UnknownArch, + .spu_2, .spirv32, .spirv64 => .UnknownArch, }; } diff --git a/src/type.zig b/src/type.zig index cb2448aa1a..e0190cdd37 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3598,6 +3598,8 @@ pub const CType = enum { .hermit, .hurd, .opencl, + .glsl450, + .vulkan, => @panic("TODO specify the C integer and float type sizes for this OS"), } } -- cgit v1.2.3 From 71ac82ecb028605545b67eaa50b34f4e2494de44 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 19 Jan 2021 01:29:01 +0100 Subject: SPIR-V: Make emitting binary more efficient --- src/codegen/spirv.zig | 6 ++++ src/link/SpirV.zig | 81 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 20 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index f0ebd49e1d..7e41913625 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -19,4 +19,10 @@ pub const SPIRVModule = struct { pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void { } + + pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void { + const word_count = @intCast(u32, args.len + 1); + try code.append((word_count << 16) | @enumToInt(instr)); + try code.appendSlice(args); + } }; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 68edfab845..03acf3c31e 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -12,7 +12,23 @@ const trace = @import("../tracy.zig").trace; const build_options = @import("build_options"); const spec = @import("../codegen/spirv/spec.zig"); -//! SPIR-V Documentation: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html +//! SPIR-V Spec documentation: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html +//! According to above documentation, a SPIR-V module has the following logical layout: +//! Header. +//! OpCapability instructions. +//! OpExtension instructions. +//! OpExtInstImport instructions. +//! A single OpMemoryModel instruction. +//! All entry points, declared with OpEntryPoint instructions. +//! All execution-mode declarators; OpExecutionMode and OpExecutionModeId instructions. +//! Debug instructions: +//! - First, OpString, OpSourceExtension, OpSource, OpSourceContinued (no forward references). +//! - OpName and OpMemberName instructions. +//! - OpModuleProcessed instructions. +//! All annotation (decoration) instructions. +//! All type declaration instructions, constant instructions, global variable declarations, (preferrably) OpUndef instructions. +//! All function declarations without a body (extern functions presumably). +//! All regular functions. pub const FnData = struct { id: ?u32 = null, @@ -103,7 +119,6 @@ pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void { decl.fn_link.spirv.code.deinit(self.base.allocator); decl.fn_link.spirv = undefined; } - pub fn flush(self: *SpirV, comp: *Compilation) !void { if (build_options.have_llvm and self.base.options.use_lld) { return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all. @@ -118,34 +133,60 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void { const module = self.base.options.module.?; - const file = self.base.file.?; - var bw = std.io.bufferedWriter(file.writer()); - const writer = bw.writer(); + var binary = std.ArrayList(u32).init(self.base.allocator); + defer binary.deinit(); // Header - // SPIR-V files support both little and big endian words. The actual format is disambiguated by - // the magic number. This backend uses little endian. - try writer.writeIntLittle(u32, spec.magic_number); - try writer.writeIntLittle(u32, (spec.version.major << 16) | (spec.version.minor) << 8); - try writer.writeIntLittle(u32, 0); // TODO: Register Zig compiler magic number. - try writer.writeIntLittle(u32, self.spirv_module.idBound()); - try writer.writeIntLittle(u32, 0); // Schema. - - // Declarations + { + const header = [_]u32{ + spec.magic_number, + (spec.version.major << 16) | (spec.version.minor << 8), + 0, // TODO: Register Zig compiler magic number. + self.spirv_module.idBound(), + 0, // Schema (currently reserved for future use in the SPIR-V spec). + }; + try binary.appendSlice(&header); + } + + // Collect list of buffers to write. + // SPIR-V files support both little and big endian words. The actual format is + // disambiguated by the magic number, and so theoretically we don't need to worry + // about endian-ness when writing the final binary. + var all_buffers = std.ArrayList(std.os.iovec_const).init(self.base.allocator); + defer all_buffers.deinit(); + + // Pre-allocate enough for the binary info + all functions + try all_buffers.ensureCapacity(module.decl_table.count() + 1); + + all_buffers.appendAssumeCapacity(wordsToIovConst(binary.items)); + + // Functions for (module.decl_table.items()) |entry| { const decl = entry.value; switch (decl.typed_value) { .most_recent => |tvm| { const fn_data = &decl.fn_link.spirv; - - // TODO: This could probably be more efficient. - for (fn_data.code.items) |word| { - try writer.writeIntLittle(u32, word); - } + all_buffers.appendAssumeCapacity(wordsToIovConst(fn_data.code.items)); }, .never_succeeded => continue, } } - try bw.flush(); + var file_size: u64 = 0; + for (all_buffers.items) |iov| { + file_size += iov.iov_len; + } + + const file = self.base.file.?; + try file.seekTo(0); + try file.setEndPos(file_size); + try file.pwritevAll(all_buffers.items, 0); +} + +fn wordsToIovConst(words: []const u32) std.os.iovec_const { + const bytes = std.mem.sliceAsBytes(words); + return .{ + .iov_base = bytes.ptr, + .iov_len = bytes.len, + }; } -- cgit v1.2.3 From 801732aebd3092c539b754170032455139c7418c Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 19 Jan 2021 01:54:01 +0100 Subject: SPIR-V: OpMemoryModel and basic capability generation --- src/codegen/spirv.zig | 12 +++++----- src/link/SpirV.zig | 61 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 18 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 7e41913625..2fe759dc03 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -3,6 +3,12 @@ const spec = @import("spirv/spec.zig"); const Module = @import("../Module.zig"); const Decl = Module.Decl; +pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void { + const word_count = @intCast(u32, args.len + 1); + try code.append((word_count << 16) | @enumToInt(instr)); + try code.appendSlice(args); +} + pub const SPIRVModule = struct { // TODO: Also use a free list. next_id: u32 = 0, @@ -19,10 +25,4 @@ pub const SPIRVModule = struct { pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void { } - - pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void { - const word_count = @intCast(u32, args.len + 1); - try code.append((word_count << 16) | @enumToInt(instr)); - try code.appendSlice(args); - } }; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 03acf3c31e..80469726e0 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -132,21 +132,24 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void { defer tracy.end(); const module = self.base.options.module.?; + const target = comp.getTarget(); var binary = std.ArrayList(u32).init(self.base.allocator); defer binary.deinit(); - // Header - { - const header = [_]u32{ - spec.magic_number, - (spec.version.major << 16) | (spec.version.minor << 8), - 0, // TODO: Register Zig compiler magic number. - self.spirv_module.idBound(), - 0, // Schema (currently reserved for future use in the SPIR-V spec). - }; - try binary.appendSlice(&header); - } + // Note: The order of adding functions to the final binary + // follows the SPIR-V logical moduel format! + + try binary.appendSlice(&[_]u32{ + spec.magic_number, + (spec.version.major << 16) | (spec.version.minor << 8), + 0, // TODO: Register Zig compiler magic number. + self.spirv_module.idBound(), + 0, // Schema (currently reserved for future use in the SPIR-V spec). + }); + + try writeCapabilities(&binary, target); + try writeMemoryModel(&binary, target); // Collect list of buffers to write. // SPIR-V files support both little and big endian words. The actual format is @@ -160,7 +163,6 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void { all_buffers.appendAssumeCapacity(wordsToIovConst(binary.items)); - // Functions for (module.decl_table.items()) |entry| { const decl = entry.value; switch (decl.typed_value) { @@ -183,6 +185,41 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void { try file.pwritevAll(all_buffers.items, 0); } +fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void { + // TODO: Integrate with a hypothetical feature system + const cap: spec.Capability = switch (target.os.tag) { + .opencl => .Kernel, + .glsl450 => .Shader, + .vulkan => .VulkanMemoryModel, + else => unreachable, // TODO + }; + + try codegen.writeInstruction(binary, .OpCapability, &[_]u32{ @enumToInt(cap) }); +} + +fn writeMemoryModel(binary: *std.ArrayList(u32), target: std.Target) !void { + const addressing_model = switch (target.os.tag) { + .opencl => switch (target.cpu.arch) { + .spirv32 => spec.AddressingModel.Physical32, + .spirv64 => spec.AddressingModel.Physical64, + else => unreachable, // TODO + }, + .glsl450, .vulkan => spec.AddressingModel.Logical, + else => unreachable, // TODO + }; + + const memory_model: spec.MemoryModel = switch (target.os.tag) { + .opencl => .OpenCL, + .glsl450 => .GLSL450, + .vulkan => .Vulkan, + else => unreachable, + }; + + try codegen.writeInstruction(binary, .OpMemoryModel, &[_]u32{ + @enumToInt(addressing_model), @enumToInt(memory_model) + }); +} + fn wordsToIovConst(words: []const u32) std.os.iovec_const { const bytes = std.mem.sliceAsBytes(words); return .{ -- cgit v1.2.3 From 1055344673a87af39f2288bae069ec9403e6086d Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 19 Jan 2021 14:28:48 +0100 Subject: SPIR-V: Use free list for result id generation --- src/codegen/spirv.zig | 25 ++++++++++++++++++++++++- src/link/SpirV.zig | 13 +++++++++---- 2 files changed, 33 insertions(+), 5 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 2fe759dc03..5a262de836 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const Allocator = std.mem.Allocator; + const spec = @import("spirv/spec.zig"); const Module = @import("../Module.zig"); const Decl = Module.Decl; @@ -10,14 +12,35 @@ pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []c } pub const SPIRVModule = struct { - // TODO: Also use a free list. next_id: u32 = 0, + free_id_list: std.ArrayList(u32), + + pub fn init(allocator: *Allocator) SPIRVModule { + return .{ + .free_id_list = std.ArrayList(u32).init(allocator), + }; + } + + pub fn deinit(self: *SPIRVModule) void { + self.free_id_list.deinit(); + } pub fn allocId(self: *SPIRVModule) u32 { + if (self.free_id_list.popOrNull()) |id| return id; + defer self.next_id += 1; return self.next_id; } + pub fn freeId(self: *SPIRVModule, id: u32) void { + if (id + 1 == self.next_id) { + self.next_id -= 1; + } else { + // If no more memory to append the id to the free list, just ignore it. + self.free_id_list.append(id) catch {}; + } + } + pub fn idBound(self: *SPIRVModule) u32 { return self.next_id; } diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 80469726e0..bde1eae391 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -38,7 +38,7 @@ pub const FnData = struct { base: link.File, // TODO: Does this file need to support multiple independent modules? -spirv_module: codegen.SPIRVModule = .{}, +spirv_module: codegen.SPIRVModule, pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV { const spirv = try gpa.create(SpirV); @@ -49,6 +49,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV { .file = null, .allocator = gpa, }, + .spirv_module = codegen.SPIRVModule.init(gpa), }; // TODO: Figure out where to put all of these @@ -87,6 +88,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio } pub fn deinit(self: *SpirV) void { + self.spirv_module.deinit(); } pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void { @@ -116,9 +118,12 @@ pub fn updateDeclExports( ) !void {} pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void { - decl.fn_link.spirv.code.deinit(self.base.allocator); + var fn_data = decl.fn_link.spirv; + fn_data.code.deinit(self.base.allocator); + if (fn_data.id) |id| self.spirv_module.freeId(id); decl.fn_link.spirv = undefined; } + pub fn flush(self: *SpirV, comp: *Compilation) !void { if (build_options.have_llvm and self.base.options.use_lld) { return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all. @@ -137,8 +142,8 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void { var binary = std.ArrayList(u32).init(self.base.allocator); defer binary.deinit(); - // Note: The order of adding functions to the final binary - // follows the SPIR-V logical moduel format! + // Note: The order of adding sections to the final binary + // follows the SPIR-V logical module format! try binary.appendSlice(&[_]u32{ spec.magic_number, -- cgit v1.2.3 From 134f5fd3d690cfdce2ab7d622cb233b3e510bf3a Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Fri, 22 Jan 2021 15:45:28 +0100 Subject: std: Update `test ""` to `test` where it makes sense --- lib/std/Thread.zig | 2 +- lib/std/build.zig | 2 +- lib/std/build/emit_raw.zig | 2 +- lib/std/c.zig | 2 +- lib/std/compress.zig | 2 +- lib/std/fs.zig | 2 +- lib/std/io.zig | 2 +- lib/std/io/c_writer.zig | 2 +- lib/std/math.zig | 2 +- lib/std/math/big.zig | 2 +- lib/std/math/big/int.zig | 2 +- lib/std/net.zig | 2 +- lib/std/os.zig | 2 +- lib/std/os/linux.zig | 2 +- lib/std/rand.zig | 2 +- lib/std/special/compiler_rt/shift.zig | 2 +- lib/std/special/compiler_rt/sparc.zig | 1 - lib/std/std.zig | 2 +- lib/std/target.zig | 2 +- lib/std/testing.zig | 2 +- lib/std/valgrind.zig | 2 +- lib/std/zig.zig | 2 +- lib/std/zig/system.zig | 2 +- src/codegen/aarch64.zig | 2 +- 24 files changed, 23 insertions(+), 24 deletions(-) (limited to 'src/codegen') diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 82111c5759..ea878bbdb0 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -553,7 +553,7 @@ pub fn getCurrentThreadId() u64 { } } -test "" { +test { if (!builtin.single_threaded) { std.testing.refAllDecls(@This()); } diff --git a/lib/std/build.zig b/lib/std/build.zig index 381488d800..3e6cf7a981 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -2985,7 +2985,7 @@ test "LibExeObjStep.addPackage" { std.testing.expectEqualStrings(pkg_top.name, dupe.name); } -test "" { +test { // The only purpose of this test is to get all these untested functions // to be referenced to avoid regression so it is okay to skip some targets. if (comptime std.Target.current.cpu.arch.ptrBitWidth() == 64) { diff --git a/lib/std/build/emit_raw.zig b/lib/std/build/emit_raw.zig index 3d2c6124c1..721b38b7a2 100644 --- a/lib/std/build/emit_raw.zig +++ b/lib/std/build/emit_raw.zig @@ -223,6 +223,6 @@ pub const InstallRawStep = struct { } }; -test "" { +test { std.testing.refAllDecls(InstallRawStep); } diff --git a/lib/std/c.zig b/lib/std/c.zig index 6b389c23ef..b5ee8cd893 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -14,7 +14,7 @@ pub const parse = @import("c/parse.zig").parse; pub const ast = @import("c/ast.zig"); pub const builtins = @import("c/builtins.zig"); -test "" { +test { _ = tokenizer; } diff --git a/lib/std/compress.zig b/lib/std/compress.zig index e7971fae8f..972031c182 100644 --- a/lib/std/compress.zig +++ b/lib/std/compress.zig @@ -9,7 +9,7 @@ pub const deflate = @import("compress/deflate.zig"); pub const gzip = @import("compress/gzip.zig"); pub const zlib = @import("compress/zlib.zig"); -test "" { +test { _ = gzip; _ = zlib; } diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 89984cda07..17c0cb7b1d 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -2475,7 +2475,7 @@ fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void { } } -test "" { +test { if (builtin.os.tag != .wasi) { _ = makeDirAbsolute; _ = makeDirAbsoluteZ; diff --git a/lib/std/io.zig b/lib/std/io.zig index e9a03445f6..240faaa452 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -171,7 +171,7 @@ test "null_writer" { null_writer.writeAll("yay" ** 10) catch |err| switch (err) {}; } -test "" { +test { _ = @import("io/bit_reader.zig"); _ = @import("io/bit_writer.zig"); _ = @import("io/buffered_atomic_file.zig"); diff --git a/lib/std/io/c_writer.zig b/lib/std/io/c_writer.zig index ec8718e381..fa7d7eb13a 100644 --- a/lib/std/io/c_writer.zig +++ b/lib/std/io/c_writer.zig @@ -34,7 +34,7 @@ fn cWriterWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!u } } -test "" { +test { if (!builtin.link_libc) return error.SkipZigTest; const filename = "tmp_io_test_file.txt"; diff --git a/lib/std/math.zig b/lib/std/math.zig index 77eed37304..de243135a1 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -278,7 +278,7 @@ pub const Complex = complex.Complex; pub const big = @import("math/big.zig"); -test "" { +test { std.testing.refAllDecls(@This()); } diff --git a/lib/std/math/big.zig b/lib/std/math/big.zig index 80649f867c..8ae214c666 100644 --- a/lib/std/math/big.zig +++ b/lib/std/math/big.zig @@ -20,7 +20,7 @@ comptime { assert(limb_info.signedness == .unsigned); } -test "" { +test { _ = int; _ = Rational; _ = Limb; diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 504083dcce..3cd72dd8e4 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -2344,6 +2344,6 @@ fn fixedIntFromSignedDoubleLimb(A: SignedDoubleLimb, storage: []Limb) Mutable { }; } -test "" { +test { _ = @import("int_test.zig"); } diff --git a/lib/std/net.zig b/lib/std/net.zig index 6fe2d1cd08..28ae2b9857 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -1769,6 +1769,6 @@ pub const StreamServer = struct { } }; -test "" { +test { _ = @import("net/test.zig"); } diff --git a/lib/std/os.zig b/lib/std/os.zig index 8c3ea7baa8..ece47828d1 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -43,7 +43,7 @@ comptime { assert(@import("std") == std); // std lib tests require --override-lib-dir } -test "" { +test { _ = darwin; _ = freebsd; _ = linux; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 37f30da1df..ffc1029708 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1355,7 +1355,7 @@ pub fn madvise(address: [*]u8, len: usize, advice: u32) usize { return syscall3(.madvise, @ptrToInt(address), len, advice); } -test "" { +test { if (builtin.os.tag == .linux) { _ = @import("linux/test.zig"); } diff --git a/lib/std/rand.zig b/lib/std/rand.zig index 8e6aab63df..d0d400b5b0 100644 --- a/lib/std/rand.zig +++ b/lib/std/rand.zig @@ -602,6 +602,6 @@ test "CSPRNG" { expect(a ^ b ^ c != 0); } -test "" { +test { std.testing.refAllDecls(@This()); } diff --git a/lib/std/special/compiler_rt/shift.zig b/lib/std/special/compiler_rt/shift.zig index 0c9938343f..46712738ab 100644 --- a/lib/std/special/compiler_rt/shift.zig +++ b/lib/std/special/compiler_rt/shift.zig @@ -124,7 +124,7 @@ pub fn __aeabi_llsr(a: i64, b: i32) callconv(.AAPCS) i64 { return __lshrdi3(a, b); } -test "" { +test { _ = @import("ashrdi3_test.zig"); _ = @import("ashrti3_test.zig"); diff --git a/lib/std/special/compiler_rt/sparc.zig b/lib/std/special/compiler_rt/sparc.zig index 72e297c514..e66bb25886 100644 --- a/lib/std/special/compiler_rt/sparc.zig +++ b/lib/std/special/compiler_rt/sparc.zig @@ -68,7 +68,6 @@ pub fn _Qp_fge(a: *f128, b: *f128) callconv(.C) bool { return cmp == @enumToInt(FCMP.Greater) or cmp == @enumToInt(FCMP.Equal); } - // Casting pub fn _Qp_dtoq(c: *f128, a: f64) callconv(.C) void { diff --git a/lib/std/std.zig b/lib/std/std.zig index d085d4fc41..780579debf 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -86,7 +86,7 @@ comptime { _ = start; } -test "" { +test { if (builtin.os.tag == .windows) { // We only test the Windows-relevant stuff to save memory because the CI // server is hitting OOM. TODO revert this after stage2 arrives. diff --git a/lib/std/target.zig b/lib/std/target.zig index 155ba046d2..70a5f08612 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1577,6 +1577,6 @@ pub const Target = struct { } }; -test "" { +test { std.testing.refAllDecls(Target.Cpu.Arch); } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 288eb5b662..26938367e9 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -443,7 +443,7 @@ fn printLine(line: []const u8) void { print("{s}\n", .{line}); } -test "" { +test { expectEqualStrings("foo", "foo"); } diff --git a/lib/std/valgrind.zig b/lib/std/valgrind.zig index 4ae273694b..6930652fbc 100644 --- a/lib/std/valgrind.zig +++ b/lib/std/valgrind.zig @@ -262,7 +262,7 @@ pub fn monitorCommand(command: [*]u8) bool { pub const memcheck = @import("valgrind/memcheck.zig"); pub const callgrind = @import("valgrind/callgrind.zig"); -test "" { +test { _ = @import("valgrind/memcheck.zig"); _ = @import("valgrind/callgrind.zig"); } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index c39eb6b05f..5b564864ad 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -255,6 +255,6 @@ test "parseCharLiteral" { std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\u{FFFF}x'", &bad_index)); } -test "" { +test { @import("std").testing.refAllDecls(@This()); } diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index d8a9998274..2d9f286dd6 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -898,6 +898,6 @@ pub const NativeTargetInfo = struct { } }; -test "" { +test { _ = @import("system/macos.zig"); } diff --git a/src/codegen/aarch64.zig b/src/codegen/aarch64.zig index e7860790f4..8abc616e2f 100644 --- a/src/codegen/aarch64.zig +++ b/src/codegen/aarch64.zig @@ -906,7 +906,7 @@ pub const Instruction = union(enum) { } }; -test "" { +test { testing.refAllDecls(@This()); } -- cgit v1.2.3 From 0d4b6ac7417d1094ac972981b0241444ce2380ba Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 22 Jan 2021 23:36:30 -0700 Subject: add LTO support The CLI gains -flto and -fno-lto options to override the default. However, the cool thing about this is that the defaults are great! In general when you use build-exe in release mode, Zig will enable LTO if it would work and it would help. zig cc supports detecting and honoring the -flto and -fno-lto flags as well. The linkWithLld functions are improved to all be the same with regards to copying the artifact instead of trying to pass single objects through LLD with -r. There is possibly a future improvement here as well; see the respective TODOs. stage1 is updated to support outputting LLVM bitcode instead of machine code when lto is enabled. This allows LLVM to optimize across the Zig and C/C++ code boundary. closes #2845 --- src/Compilation.zig | 37 +++ src/clang_options_data.zig | 27 +- src/codegen/llvm/bindings.zig | 8 +- src/link.zig | 1 + src/link/Coff.zig | 7 + src/link/Elf.zig | 604 ++++++++++++++++++++++------------------- src/link/MachO.zig | 7 + src/link/Wasm.zig | 237 +++++++++------- src/main.zig | 12 + src/stage1.zig | 1 + src/stage1/all_types.hpp | 1 + src/stage1/codegen.cpp | 10 +- src/stage1/stage1.cpp | 1 + src/stage1/stage1.h | 1 + src/zig_llvm.cpp | 10 +- src/zig_llvm.h | 2 +- tools/update_clang_options.zig | 8 + 17 files changed, 576 insertions(+), 398 deletions(-) (limited to 'src/codegen') diff --git a/src/Compilation.zig b/src/Compilation.zig index fae725a1ac..b6c82c2dbb 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -444,6 +444,7 @@ pub const InitOptions = struct { want_valgrind: ?bool = null, want_tsan: ?bool = null, want_compiler_rt: ?bool = null, + want_lto: ?bool = null, use_llvm: ?bool = null, use_lld: ?bool = null, use_clang: ?bool = null, @@ -602,6 +603,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (ofmt == .c) break :blk false; + if (options.want_lto) |lto| { + if (lto) { + break :blk true; + } + } + // Our linker can't handle objects or most advanced options yet. if (options.link_objects.len != 0 or options.c_source_files.len != 0 or @@ -647,6 +654,26 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { break :outer opts; } else .{}; + const lto = blk: { + if (options.want_lto) |explicit| { + if (!use_lld) + return error.LtoUnavailableWithoutLld; + break :blk explicit; + } else if (!use_lld) { + break :blk false; + } else if (options.c_source_files.len == 0) { + break :blk false; + } else if (darwin_options.system_linker_hack) { + break :blk false; + } else switch (options.output_mode) { + .Lib, .Obj => break :blk false, + .Exe => switch (options.optimize_mode) { + .Debug => break :blk false, + .ReleaseSafe, .ReleaseFast, .ReleaseSmall => break :blk true, + }, + } + }; + const tsan = options.want_tsan orelse false; const link_libc = options.link_libc or target_util.osRequiresLibC(options.target) or tsan; @@ -821,6 +848,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { cache.hash.add(ofmt); cache.hash.add(pic); cache.hash.add(pie); + cache.hash.add(lto); cache.hash.add(tsan); cache.hash.add(stack_check); cache.hash.add(red_zone); @@ -1022,6 +1050,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .libc_installation = libc_dirs.libc_installation, .pic = pic, .pie = pie, + .lto = lto, .valgrind = valgrind, .tsan = tsan, .stack_check = stack_check, @@ -2233,6 +2262,9 @@ pub fn addCCArgs( "-nostdinc", "-fno-spell-checking", }); + if (comp.bin_file.options.lto) { + try argv.append("-flto"); + } // According to Rich Felker libc headers are supposed to go before C language headers. // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics @@ -3255,6 +3287,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node .err_color = @enumToInt(comp.color), .pic = comp.bin_file.options.pic, .pie = comp.bin_file.options.pie, + .lto = comp.bin_file.options.lto, .link_libc = comp.bin_file.options.link_libc, .link_libcpp = comp.bin_file.options.link_libcpp, .strip = comp.bin_file.options.strip, @@ -3415,6 +3448,10 @@ pub fn build_crt_file( .want_tsan = false, .want_pic = comp.bin_file.options.pic, .want_pie = comp.bin_file.options.pie, + .want_lto = switch (output_mode) { + .Lib => comp.bin_file.options.lto, + .Obj, .Exe => false, + }, .emit_h = null, .strip = comp.compilerRtStrip(), .is_native_os = comp.bin_file.options.is_native_os, diff --git a/src/clang_options_data.zig b/src/clang_options_data.zig index c901c6045f..4d89308545 100644 --- a/src/clang_options_data.zig +++ b/src/clang_options_data.zig @@ -2732,7 +2732,14 @@ flagpd1("fkeep-static-consts"), flagpd1("flat_namespace"), flagpd1("flax-vector-conversions"), flagpd1("flimit-debug-info"), -flagpd1("flto"), +.{ + .name = "flto", + .syntax = .flag, + .zig_equivalent = .lto, + .pd1 = true, + .pd2 = false, + .psl = false, +}, flagpd1("flto-unit"), flagpd1("flto-visibility-public-std"), sepd1("fmacro-backtrace-limit"), @@ -2942,7 +2949,14 @@ flagpd1("fno-jump-tables"), flagpd1("fno-keep-static-consts"), flagpd1("fno-lax-vector-conversions"), flagpd1("fno-limit-debug-info"), -flagpd1("fno-lto"), +.{ + .name = "fno-lto", + .syntax = .flag, + .zig_equivalent = .no_lto, + .pd1 = true, + .pd2 = false, + .psl = false, +}, flagpd1("fno-lto-unit"), flagpd1("fno-math-builtin"), flagpd1("fno-math-errno"), @@ -5638,7 +5652,14 @@ jspd1("Ttext"), .pd2 = true, .psl = false, }, -joinpd1("flto="), +.{ + .name = "flto=", + .syntax = .joined, + .zig_equivalent = .lto, + .pd1 = true, + .pd2 = false, + .psl = false, +}, joinpd1("gcoff"), joinpd1("mabi="), joinpd1("mabs="), diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 8774260a08..6474957b1c 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -243,7 +243,13 @@ pub const TargetMachine = opaque { extern fn LLVMDisposeTargetMachine(T: *const TargetMachine) void; pub const emitToFile = LLVMTargetMachineEmitToFile; - extern fn LLVMTargetMachineEmitToFile(*const TargetMachine, M: *const Module, Filename: [*:0]const u8, codegen: CodeGenFileType, ErrorMessage: *[*:0]const u8) LLVMBool; + extern fn LLVMTargetMachineEmitToFile( + *const TargetMachine, + M: *const Module, + Filename: [*:0]const u8, + codegen: CodeGenFileType, + ErrorMessage: *[*:0]const u8, + ) LLVMBool; }; pub const CodeMode = extern enum { diff --git a/src/link.zig b/src/link.zig index 6914131bea..16222b5a79 100644 --- a/src/link.zig +++ b/src/link.zig @@ -74,6 +74,7 @@ pub const Options = struct { is_native_abi: bool, pic: bool, pie: bool, + lto: bool, valgrind: bool, tsan: bool, stack_check: bool, diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 981d4ec3a3..1acf09a1dc 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -945,6 +945,13 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { if (!self.base.options.strip) { try argv.append("-DEBUG"); } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-OPT:lldlto=2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"), + } + } if (self.base.options.output_mode == .Exe) { const stack_size = self.base.options.stack_size_override orelse 16777216; try argv.append(try allocPrint(arena, "-STACK:{d}", .{stack_size})); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index fffe5628fe..18f3f57712 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1384,351 +1384,385 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { }; } - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(self.base.allocator); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "ld.lld" }); - if (is_obj) { - try argv.append("-r"); - } + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); + if (self.base.options.output_mode == .Obj and self.base.options.lto) { + // In this case we must do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (self.base.options.objects.len != 0) + break :blk self.base.options.objects[0]; + + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.items()[0].key.status.success.object_path; + + if (module_obj_path) |p| + break :blk p; + + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + // This can happen when using --enable-cache and using the stage1 backend. In this case + // we can skip the file copy. + if (!mem.eql(u8, the_object_path, full_out_path)) { + try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); + } + } else { - try argv.append("-error-limit=0"); + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "ld.lld" }); + if (is_obj) { + try argv.append("-r"); + } - if (self.base.options.output_mode == .Exe) { - try argv.append("-z"); - try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size})); - } + try argv.append("-error-limit=0"); - if (self.base.options.image_base_override) |image_base| { - try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{image_base})); - } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + } - if (self.base.options.linker_script) |linker_script| { - try argv.append("-T"); - try argv.append(linker_script); - } + if (self.base.options.output_mode == .Exe) { + try argv.append("-z"); + try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size})); + } - if (gc_sections) { - try argv.append("--gc-sections"); - } + if (self.base.options.image_base_override) |image_base| { + try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{image_base})); + } - if (self.base.options.eh_frame_hdr) { - try argv.append("--eh-frame-hdr"); - } + if (self.base.options.linker_script) |linker_script| { + try argv.append("-T"); + try argv.append(linker_script); + } - if (self.base.options.emit_relocs) { - try argv.append("--emit-relocs"); - } + if (gc_sections) { + try argv.append("--gc-sections"); + } - if (self.base.options.rdynamic) { - try argv.append("--export-dynamic"); - } + if (self.base.options.eh_frame_hdr) { + try argv.append("--eh-frame-hdr"); + } - try argv.appendSlice(self.base.options.extra_lld_args); + if (self.base.options.emit_relocs) { + try argv.append("--emit-relocs"); + } - if (self.base.options.z_nodelete) { - try argv.append("-z"); - try argv.append("nodelete"); - } - if (self.base.options.z_defs) { - try argv.append("-z"); - try argv.append("defs"); - } + if (self.base.options.rdynamic) { + try argv.append("--export-dynamic"); + } - if (getLDMOption(target)) |ldm| { - // Any target ELF will use the freebsd osabi if suffixed with "_fbsd". - const arg = if (target.os.tag == .freebsd) - try std.fmt.allocPrint(arena, "{s}_fbsd", .{ldm}) - else - ldm; - try argv.append("-m"); - try argv.append(arg); - } + try argv.appendSlice(self.base.options.extra_lld_args); - if (self.base.options.link_mode == .Static) { - if (target.cpu.arch.isARM() or target.cpu.arch.isThumb()) { - try argv.append("-Bstatic"); - } else { - try argv.append("-static"); + if (self.base.options.z_nodelete) { + try argv.append("-z"); + try argv.append("nodelete"); + } + if (self.base.options.z_defs) { + try argv.append("-z"); + try argv.append("defs"); } - } else if (is_dyn_lib) { - try argv.append("-shared"); - } - if (self.base.options.pie and self.base.options.output_mode == .Exe) { - try argv.append("-pie"); - } + if (getLDMOption(target)) |ldm| { + // Any target ELF will use the freebsd osabi if suffixed with "_fbsd". + const arg = if (target.os.tag == .freebsd) + try std.fmt.allocPrint(arena, "{s}_fbsd", .{ldm}) + else + ldm; + try argv.append("-m"); + try argv.append(arg); + } - const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - try argv.append("-o"); - try argv.append(full_out_path); + if (self.base.options.link_mode == .Static) { + if (target.cpu.arch.isARM() or target.cpu.arch.isThumb()) { + try argv.append("-Bstatic"); + } else { + try argv.append("-static"); + } + } else if (is_dyn_lib) { + try argv.append("-shared"); + } - if (link_in_crt) { - const crt1o: []const u8 = o: { - if (target.os.tag == .netbsd) { - break :o "crt0.o"; - } else if (target.os.tag == .openbsd) { - if (self.base.options.link_mode == .Static) { - break :o "rcrt0.o"; - } else { + if (self.base.options.pie and self.base.options.output_mode == .Exe) { + try argv.append("-pie"); + } + + try argv.append("-o"); + try argv.append(full_out_path); + + if (link_in_crt) { + const crt1o: []const u8 = o: { + if (target.os.tag == .netbsd) { break :o "crt0.o"; - } - } else if (target.isAndroid()) { - if (self.base.options.link_mode == .Dynamic) { - break :o "crtbegin_dynamic.o"; - } else { - break :o "crtbegin_static.o"; - } - } else if (self.base.options.link_mode == .Static) { - if (self.base.options.pie) { - break :o "rcrt1.o"; + } else if (target.os.tag == .openbsd) { + if (self.base.options.link_mode == .Static) { + break :o "rcrt0.o"; + } else { + break :o "crt0.o"; + } + } else if (target.isAndroid()) { + if (self.base.options.link_mode == .Dynamic) { + break :o "crtbegin_dynamic.o"; + } else { + break :o "crtbegin_static.o"; + } + } else if (self.base.options.link_mode == .Static) { + if (self.base.options.pie) { + break :o "rcrt1.o"; + } else { + break :o "crt1.o"; + } } else { - break :o "crt1.o"; + break :o "Scrt1.o"; } - } else { - break :o "Scrt1.o"; + }; + try argv.append(try comp.get_libc_crt_file(arena, crt1o)); + if (target_util.libc_needs_crti_crtn(target)) { + try argv.append(try comp.get_libc_crt_file(arena, "crti.o")); + } + if (target.os.tag == .openbsd) { + try argv.append(try comp.get_libc_crt_file(arena, "crtbegin.o")); } - }; - try argv.append(try comp.get_libc_crt_file(arena, crt1o)); - if (target_util.libc_needs_crti_crtn(target)) { - try argv.append(try comp.get_libc_crt_file(arena, "crti.o")); - } - if (target.os.tag == .openbsd) { - try argv.append(try comp.get_libc_crt_file(arena, "crtbegin.o")); } - } - // rpaths - var rpath_table = std.StringHashMap(void).init(self.base.allocator); - defer rpath_table.deinit(); - for (self.base.options.rpath_list) |rpath| { - if ((try rpath_table.fetchPut(rpath, {})) == null) { - try argv.append("-rpath"); - try argv.append(rpath); + // rpaths + var rpath_table = std.StringHashMap(void).init(self.base.allocator); + defer rpath_table.deinit(); + for (self.base.options.rpath_list) |rpath| { + if ((try rpath_table.fetchPut(rpath, {})) == null) { + try argv.append("-rpath"); + try argv.append(rpath); + } } - } - if (self.base.options.each_lib_rpath) { - var test_path = std.ArrayList(u8).init(self.base.allocator); - defer test_path.deinit(); - for (self.base.options.lib_dirs) |lib_dir_path| { - for (self.base.options.system_libs.items()) |entry| { - const link_lib = entry.key; - test_path.shrinkRetainingCapacity(0); - const sep = fs.path.sep_str; - try test_path.writer().print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, link_lib }); - fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| return e, - }; - if ((try rpath_table.fetchPut(lib_dir_path, {})) == null) { - try argv.append("-rpath"); - try argv.append(lib_dir_path); + if (self.base.options.each_lib_rpath) { + var test_path = std.ArrayList(u8).init(self.base.allocator); + defer test_path.deinit(); + for (self.base.options.lib_dirs) |lib_dir_path| { + for (self.base.options.system_libs.items()) |entry| { + const link_lib = entry.key; + test_path.shrinkRetainingCapacity(0); + const sep = fs.path.sep_str; + try test_path.writer().print("{s}" ++ sep ++ "lib{s}.so", .{ lib_dir_path, link_lib }); + fs.cwd().access(test_path.items, .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + if ((try rpath_table.fetchPut(lib_dir_path, {})) == null) { + try argv.append("-rpath"); + try argv.append(lib_dir_path); + } } } } - } - for (self.base.options.lib_dirs) |lib_dir| { - try argv.append("-L"); - try argv.append(lib_dir); - } - - if (self.base.options.link_libc) { - if (self.base.options.libc_installation) |libc_installation| { + for (self.base.options.lib_dirs) |lib_dir| { try argv.append("-L"); - try argv.append(libc_installation.crt_dir.?); + try argv.append(lib_dir); } - if (have_dynamic_linker) { - if (self.base.options.dynamic_linker) |dynamic_linker| { - try argv.append("-dynamic-linker"); - try argv.append(dynamic_linker); + if (self.base.options.link_libc) { + if (self.base.options.libc_installation) |libc_installation| { + try argv.append("-L"); + try argv.append(libc_installation.crt_dir.?); } - } - } - if (is_dyn_lib) { - if (self.base.options.soname) |soname| { - try argv.append("-soname"); - try argv.append(soname); - } - if (self.base.options.version_script) |version_script| { - try argv.append("-version-script"); - try argv.append(version_script); + if (have_dynamic_linker) { + if (self.base.options.dynamic_linker) |dynamic_linker| { + try argv.append("-dynamic-linker"); + try argv.append(dynamic_linker); + } + } } - } - - // Positional arguments to the linker such as object files. - try argv.appendSlice(self.base.options.objects); - for (comp.c_object_table.items()) |entry| { - try argv.append(entry.key.status.success.object_path); - } + if (is_dyn_lib) { + if (self.base.options.soname) |soname| { + try argv.append("-soname"); + try argv.append(soname); + } + if (self.base.options.version_script) |version_script| { + try argv.append("-version-script"); + try argv.append(version_script); + } + } - if (module_obj_path) |p| { - try argv.append(p); - } + // Positional arguments to the linker such as object files. + try argv.appendSlice(self.base.options.objects); - // TSAN - if (self.base.options.tsan) { - try argv.append(comp.tsan_static_lib.?.full_object_path); - } + for (comp.c_object_table.items()) |entry| { + try argv.append(entry.key.status.success.object_path); + } - // libc - // TODO: enable when stage2 can build c.zig - if (is_exe_or_dyn_lib and - !self.base.options.skip_linker_dependencies and - !self.base.options.link_libc and - build_options.is_stage1) - { - try argv.append(comp.libc_static_lib.?.full_object_path); - } + if (module_obj_path) |p| { + try argv.append(p); + } - // compiler-rt - if (compiler_rt_path) |p| { - try argv.append(p); - } + // TSAN + if (self.base.options.tsan) { + try argv.append(comp.tsan_static_lib.?.full_object_path); + } - // Shared libraries. - if (is_exe_or_dyn_lib) { - const system_libs = self.base.options.system_libs.items(); - try argv.ensureCapacity(argv.items.len + system_libs.len); - for (system_libs) |entry| { - const link_lib = entry.key; - // By this time, we depend on these libs being dynamically linked libraries and not static libraries - // (the check for that needs to be earlier), but they could be full paths to .so files, in which - // case we want to avoid prepending "-l". - const ext = Compilation.classifyFileExt(link_lib); - const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{s}", .{link_lib}); - argv.appendAssumeCapacity(arg); + // libc + // TODO: enable when stage2 can build c.zig + if (is_exe_or_dyn_lib and + !self.base.options.skip_linker_dependencies and + !self.base.options.link_libc and + build_options.is_stage1) + { + try argv.append(comp.libc_static_lib.?.full_object_path); } - // libc++ dep - if (self.base.options.link_libcpp) { - try argv.append(comp.libcxxabi_static_lib.?.full_object_path); - try argv.append(comp.libcxx_static_lib.?.full_object_path); + // compiler-rt + if (compiler_rt_path) |p| { + try argv.append(p); } - // libc dep - if (self.base.options.link_libc) { - if (self.base.options.libc_installation != null) { - if (self.base.options.link_mode == .Static) { - try argv.append("--start-group"); - try argv.append("-lc"); - try argv.append("-lm"); - try argv.append("--end-group"); - } else { - try argv.append("-lc"); - try argv.append("-lm"); - } + // Shared libraries. + if (is_exe_or_dyn_lib) { + const system_libs = self.base.options.system_libs.items(); + try argv.ensureCapacity(argv.items.len + system_libs.len); + for (system_libs) |entry| { + const link_lib = entry.key; + // By this time, we depend on these libs being dynamically linked libraries and not static libraries + // (the check for that needs to be earlier), but they could be full paths to .so files, in which + // case we want to avoid prepending "-l". + const ext = Compilation.classifyFileExt(link_lib); + const arg = if (ext == .shared_library) link_lib else try std.fmt.allocPrint(arena, "-l{s}", .{link_lib}); + argv.appendAssumeCapacity(arg); + } - if (target.os.tag == .freebsd or target.os.tag == .netbsd or target.os.tag == .openbsd) { - try argv.append("-lpthread"); - } - } else if (target.isGnuLibC()) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - for (glibc.libs) |lib| { - const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{ - comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, - }); - try argv.append(lib_path); + // libc++ dep + if (self.base.options.link_libcpp) { + try argv.append(comp.libcxxabi_static_lib.?.full_object_path); + try argv.append(comp.libcxx_static_lib.?.full_object_path); + } + + // libc dep + if (self.base.options.link_libc) { + if (self.base.options.libc_installation != null) { + if (self.base.options.link_mode == .Static) { + try argv.append("--start-group"); + try argv.append("-lc"); + try argv.append("-lm"); + try argv.append("--end-group"); + } else { + try argv.append("-lc"); + try argv.append("-lm"); + } + + if (target.os.tag == .freebsd or target.os.tag == .netbsd or target.os.tag == .openbsd) { + try argv.append("-lpthread"); + } + } else if (target.isGnuLibC()) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + for (glibc.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{ + comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a")); + } else if (target.isMusl()) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + try argv.append(try comp.get_libc_crt_file(arena, switch (self.base.options.link_mode) { + .Static => "libc.a", + .Dynamic => "libc.so", + })); + } else if (self.base.options.link_libcpp) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + } else { + unreachable; // Compiler was supposed to emit an error for not being able to provide libc. } - try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a")); - } else if (target.isMusl()) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - try argv.append(try comp.get_libc_crt_file(arena, switch (self.base.options.link_mode) { - .Static => "libc.a", - .Dynamic => "libc.so", - })); - } else if (self.base.options.link_libcpp) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - } else { - unreachable; // Compiler was supposed to emit an error for not being able to provide libc. } } - } - // crt end - if (link_in_crt) { - if (target.isAndroid()) { - try argv.append(try comp.get_libc_crt_file(arena, "crtend_android.o")); - } else if (target.os.tag == .openbsd) { - try argv.append(try comp.get_libc_crt_file(arena, "crtend.o")); - } else if (target_util.libc_needs_crti_crtn(target)) { - try argv.append(try comp.get_libc_crt_file(arena, "crtn.o")); + // crt end + if (link_in_crt) { + if (target.isAndroid()) { + try argv.append(try comp.get_libc_crt_file(arena, "crtend_android.o")); + } else if (target.os.tag == .openbsd) { + try argv.append(try comp.get_libc_crt_file(arena, "crtend.o")); + } else if (target_util.libc_needs_crti_crtn(target)) { + try argv.append(try comp.get_libc_crt_file(arena, "crtn.o")); + } } - } - if (allow_shlib_undefined) { - try argv.append("--allow-shlib-undefined"); - } + if (allow_shlib_undefined) { + try argv.append("--allow-shlib-undefined"); + } - if (self.base.options.bind_global_refs_locally) { - try argv.append("-Bsymbolic"); - } + if (self.base.options.bind_global_refs_locally) { + try argv.append("-Bsymbolic"); + } - if (self.base.options.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } + if (self.base.options.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv.items[1..]); + } - // Sadly, we must run LLD as a child process because it does not behave - // properly as a library. - const child = try std.ChildProcess.init(argv.items, arena); - defer child.deinit(); + // Sadly, we must run LLD as a child process because it does not behave + // properly as a library. + const child = try std.ChildProcess.init(argv.items, arena); + defer child.deinit(); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + if (comp.clang_passthrough_mode) { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; - const term = child.spawnAndWait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO https://github.com/ziglang/zig/issues/6342 - std.process.exit(1); - } - }, - else => std.process.abort(), - } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; + const term = child.spawnAndWait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO https://github.com/ziglang/zig/issues/6342 + std.process.exit(1); + } + }, + else => std.process.abort(), + } + } else { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; - try child.spawn(); + try child.spawn(); - const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); - const term = child.wait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; + const term = child.wait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO parse this output and surface with the Compilation API rather than - // directly outputting to stderr here. - std.debug.print("{s}", .{stderr}); - return error.LLDReportedFailure; - } - }, - else => { - log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - return error.LLDCrashed; - }, - } + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO parse this output and surface with the Compilation API rather than + // directly outputting to stderr here. + std.debug.print("{s}", .{stderr}); + return error.LLDReportedFailure; + } + }, + else => { + log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); + return error.LLDCrashed; + }, + } - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + if (stderr.len != 0) { + log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + } } } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6c2059fcdc..eee5841903 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -620,6 +620,13 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try argv.append("0"); } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + } try argv.append("-demangle"); if (self.base.options.rdynamic and !self.base.options.system_linker_hack) { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 4640c9f1af..fb356ad1aa 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -362,122 +362,157 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { }; } - const is_obj = self.base.options.output_mode == .Obj; - - // Create an LLD command line and invoke it. - var argv = std.ArrayList([]const u8).init(self.base.allocator); - defer argv.deinit(); - // We will invoke ourselves as a child process to gain access to LLD. - // This is necessary because LLD does not behave properly as a library - - // it calls exit() and does not reset all global data between invocations. - try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "wasm-ld" }); - if (is_obj) { - try argv.append("-r"); - } - - try argv.append("-error-limit=0"); + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); - if (self.base.options.output_mode == .Exe) { - // Increase the default stack size to a more reasonable value of 1MB instead of - // the default of 1 Wasm page being 64KB, unless overriden by the user. - try argv.append("-z"); - const stack_size = self.base.options.stack_size_override orelse 1048576; - const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); - try argv.append(arg); - - // Put stack before globals so that stack overflow results in segfault immediately - // before corrupting globals. See https://github.com/ziglang/zig/issues/4496 - try argv.append("--stack-first"); - } else { - try argv.append("--no-entry"); // So lld doesn't look for _start. - try argv.append("--export-all"); - } - try argv.appendSlice(&[_][]const u8{ - "--allow-undefined", - "-o", - try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}), - }); + if (self.base.options.output_mode == .Obj) { + // LLD's WASM driver does not support the equvialent of `-r` so we do a simple file copy + // here. TODO: think carefully about how we can avoid this redundant operation when doing + // build-obj. See also the corresponding TODO in linkAsArchive. + const the_object_path = blk: { + if (self.base.options.objects.len != 0) + break :blk self.base.options.objects[0]; - // Positional arguments to the linker such as object files. - try argv.appendSlice(self.base.options.objects); + if (comp.c_object_table.count() != 0) + break :blk comp.c_object_table.items()[0].key.status.success.object_path; - for (comp.c_object_table.items()) |entry| { - try argv.append(entry.key.status.success.object_path); - } - if (module_obj_path) |p| { - try argv.append(p); - } + if (module_obj_path) |p| + break :blk p; - if (self.base.options.output_mode != .Obj and - !self.base.options.skip_linker_dependencies and - !self.base.options.link_libc) - { - try argv.append(comp.libc_static_lib.?.full_object_path); - } + // TODO I think this is unreachable. Audit this situation when solving the above TODO + // regarding eliding redundant object -> object transformations. + return error.NoObjectsToLink; + }; + // This can happen when using --enable-cache and using the stage1 backend. In this case + // we can skip the file copy. + if (!mem.eql(u8, the_object_path, full_out_path)) { + try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); + } + } else { + const is_obj = self.base.options.output_mode == .Obj; + + // Create an LLD command line and invoke it. + var argv = std.ArrayList([]const u8).init(self.base.allocator); + defer argv.deinit(); + // We will invoke ourselves as a child process to gain access to LLD. + // This is necessary because LLD does not behave properly as a library - + // it calls exit() and does not reset all global data between invocations. + try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "wasm-ld" }); + if (is_obj) { + try argv.append("-r"); + } - if (compiler_rt_path) |p| { - try argv.append(p); - } + try argv.append("-error-limit=0"); - if (self.base.options.verbose_link) { - // Skip over our own name so that the LLD linker name is the first argv item. - Compilation.dump_argv(argv.items[1..]); - } + if (self.base.options.lto) { + switch (self.base.options.optimize_mode) { + .Debug => {}, + .ReleaseSmall => try argv.append("-O2"), + .ReleaseFast, .ReleaseSafe => try argv.append("-O3"), + } + } - // Sadly, we must run LLD as a child process because it does not behave - // properly as a library. - const child = try std.ChildProcess.init(argv.items, arena); - defer child.deinit(); + if (self.base.options.output_mode == .Exe) { + // Increase the default stack size to a more reasonable value of 1MB instead of + // the default of 1 Wasm page being 64KB, unless overriden by the user. + try argv.append("-z"); + const stack_size = self.base.options.stack_size_override orelse 1048576; + const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); + try argv.append(arg); + + // Put stack before globals so that stack overflow results in segfault immediately + // before corrupting globals. See https://github.com/ziglang/zig/issues/4496 + try argv.append("--stack-first"); + } else { + try argv.append("--no-entry"); // So lld doesn't look for _start. + try argv.append("--export-all"); + } + try argv.appendSlice(&[_][]const u8{ + "--allow-undefined", + "-o", + full_out_path, + }); - if (comp.clang_passthrough_mode) { - child.stdin_behavior = .Inherit; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + // Positional arguments to the linker such as object files. + try argv.appendSlice(self.base.options.objects); - const term = child.spawnAndWait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO https://github.com/ziglang/zig/issues/6342 - std.process.exit(1); - } - }, - else => std.process.abort(), + for (comp.c_object_table.items()) |entry| { + try argv.append(entry.key.status.success.object_path); + } + if (module_obj_path) |p| { + try argv.append(p); } - } else { - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Ignore; - child.stderr_behavior = .Pipe; - - try child.spawn(); - const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + if (self.base.options.output_mode != .Obj and + !self.base.options.skip_linker_dependencies and + !self.base.options.link_libc) + { + try argv.append(comp.libc_static_lib.?.full_object_path); + } - const term = child.wait() catch |err| { - log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); - return error.UnableToSpawnSelf; - }; + if (compiler_rt_path) |p| { + try argv.append(p); + } - switch (term) { - .Exited => |code| { - if (code != 0) { - // TODO parse this output and surface with the Compilation API rather than - // directly outputting to stderr here. - std.debug.print("{s}", .{stderr}); - return error.LLDReportedFailure; - } - }, - else => { - log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); - return error.LLDCrashed; - }, + if (self.base.options.verbose_link) { + // Skip over our own name so that the LLD linker name is the first argv item. + Compilation.dump_argv(argv.items[1..]); } - if (stderr.len != 0) { - log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + // Sadly, we must run LLD as a child process because it does not behave + // properly as a library. + const child = try std.ChildProcess.init(argv.items, arena); + defer child.deinit(); + + if (comp.clang_passthrough_mode) { + child.stdin_behavior = .Inherit; + child.stdout_behavior = .Inherit; + child.stderr_behavior = .Inherit; + + const term = child.spawnAndWait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO https://github.com/ziglang/zig/issues/6342 + std.process.exit(1); + } + }, + else => std.process.abort(), + } + } else { + child.stdin_behavior = .Ignore; + child.stdout_behavior = .Ignore; + child.stderr_behavior = .Pipe; + + try child.spawn(); + + const stderr = try child.stderr.?.reader().readAllAlloc(arena, 10 * 1024 * 1024); + + const term = child.wait() catch |err| { + log.err("unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); + return error.UnableToSpawnSelf; + }; + + switch (term) { + .Exited => |code| { + if (code != 0) { + // TODO parse this output and surface with the Compilation API rather than + // directly outputting to stderr here. + std.debug.print("{s}", .{stderr}); + return error.LLDReportedFailure; + } + }, + else => { + log.err("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr }); + return error.LLDCrashed; + }, + } + + if (stderr.len != 0) { + log.warn("unexpected LLD stderr:\n{s}", .{stderr}); + } } } diff --git a/src/main.zig b/src/main.zig index ceada7e5d2..32c35739cd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -287,6 +287,8 @@ const usage_build_generic = \\ -fno-PIC Force-disable Position Independent Code \\ -fPIE Force-enable Position Independent Executable \\ -fno-PIE Force-disable Position Independent Executable + \\ -flto Force-enable Link Time Optimization (requires LLVM extensions) + \\ -fno-lto Force-disable Link Time Optimization \\ -fstack-check Enable stack probing in unsafe builds \\ -fno-stack-check Disable stack probing in safe builds \\ -fsanitize-c Enable C undefined behavior detection in unsafe builds @@ -511,6 +513,7 @@ fn buildOutputType( var enable_cache: ?bool = null; var want_pic: ?bool = null; var want_pie: ?bool = null; + var want_lto: ?bool = null; var want_sanitize_c: ?bool = null; var want_stack_check: ?bool = null; var want_red_zone: ?bool = null; @@ -852,6 +855,10 @@ fn buildOutputType( want_pie = true; } else if (mem.eql(u8, arg, "-fno-PIE")) { want_pie = false; + } else if (mem.eql(u8, arg, "-flto")) { + want_lto = true; + } else if (mem.eql(u8, arg, "-fno-lto")) { + want_lto = false; } else if (mem.eql(u8, arg, "-fstack-check")) { want_stack_check = true; } else if (mem.eql(u8, arg, "-fno-stack-check")) { @@ -1085,6 +1092,8 @@ fn buildOutputType( .no_pic => want_pic = false, .pie => want_pie = true, .no_pie => want_pie = false, + .lto => want_lto = true, + .no_lto => want_lto = false, .red_zone => want_red_zone = true, .no_red_zone => want_red_zone = false, .nostdlib => ensure_libc_on_non_freestanding = false, @@ -1771,6 +1780,7 @@ fn buildOutputType( .link_libcpp = link_libcpp, .want_pic = want_pic, .want_pie = want_pie, + .want_lto = want_lto, .want_sanitize_c = want_sanitize_c, .want_stack_check = want_stack_check, .want_red_zone = want_red_zone, @@ -2952,6 +2962,8 @@ pub const ClangArgIterator = struct { no_pic, pie, no_pie, + lto, + no_lto, nostdlib, nostdlib_cpp, shared, diff --git a/src/stage1.zig b/src/stage1.zig index 44fd8e109e..8ab3b1d94d 100644 --- a/src/stage1.zig +++ b/src/stage1.zig @@ -109,6 +109,7 @@ pub const Module = extern struct { err_color: ErrColor, pic: bool, pie: bool, + lto: bool, link_libc: bool, link_libcpp: bool, strip: bool, diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index c235bb362d..726d1a3aaf 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -2192,6 +2192,7 @@ struct CodeGen { bool is_single_threaded; bool have_pic; bool have_pie; + bool have_lto; bool link_mode_dynamic; bool dll_export_fns; bool have_stack_probing; diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 7a973285df..d850b3ee31 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -8449,8 +8449,9 @@ static void zig_llvm_emit_output(CodeGen *g) { // Unfortunately, LLVM shits the bed when we ask for both binary and assembly. So we call the entire // pipeline multiple times if this is requested. if (asm_filename != nullptr && bin_filename != nullptr) { - if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, g->build_mode == BuildModeDebug, - is_small, g->enable_time_report, g->tsan_enabled, nullptr, bin_filename, llvm_ir_filename)) + if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, + g->build_mode == BuildModeDebug, is_small, g->enable_time_report, g->tsan_enabled, + g->have_lto, nullptr, bin_filename, llvm_ir_filename)) { fprintf(stderr, "LLVM failed to emit file: %s\n", err_msg); exit(1); @@ -8459,8 +8460,9 @@ static void zig_llvm_emit_output(CodeGen *g) { llvm_ir_filename = nullptr; } - if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, g->build_mode == BuildModeDebug, - is_small, g->enable_time_report, g->tsan_enabled, asm_filename, bin_filename, llvm_ir_filename)) + if (ZigLLVMTargetMachineEmitToFile(g->target_machine, g->module, &err_msg, + g->build_mode == BuildModeDebug, is_small, g->enable_time_report, g->tsan_enabled, + g->have_lto, asm_filename, bin_filename, llvm_ir_filename)) { fprintf(stderr, "LLVM failed to emit file: %s\n", err_msg); exit(1); diff --git a/src/stage1/stage1.cpp b/src/stage1/stage1.cpp index d680121577..39e3bad1a5 100644 --- a/src/stage1/stage1.cpp +++ b/src/stage1/stage1.cpp @@ -90,6 +90,7 @@ void zig_stage1_build_object(struct ZigStage1 *stage1) { g->dll_export_fns = stage1->dll_export_fns; g->have_pic = stage1->pic; g->have_pie = stage1->pie; + g->have_lto = stage1->lto; g->have_stack_probing = stage1->enable_stack_probing; g->red_zone = stage1->red_zone; g->is_single_threaded = stage1->is_single_threaded; diff --git a/src/stage1/stage1.h b/src/stage1/stage1.h index 6db3621cf5..7fa1576d64 100644 --- a/src/stage1/stage1.h +++ b/src/stage1/stage1.h @@ -178,6 +178,7 @@ struct ZigStage1 { bool pic; bool pie; + bool lto; bool link_libc; bool link_libcpp; bool strip; diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index 51af5e06d1..280920ac74 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -184,7 +185,7 @@ unsigned ZigLLVMDataLayoutGetProgramAddressSpace(LLVMTargetDataRef TD) { bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref, char **error_message, bool is_debug, - bool is_small, bool time_report, bool tsan, + bool is_small, bool time_report, bool tsan, bool lto, const char *asm_filename, const char *bin_filename, const char *llvm_ir_filename) { TimePassesIsEnabled = time_report; @@ -234,7 +235,7 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM PMBuilder->VerifyInput = assertions_on; PMBuilder->VerifyOutput = assertions_on; PMBuilder->MergeFunctions = !is_debug; - PMBuilder->PrepareForLTO = false; + PMBuilder->PrepareForLTO = lto; PMBuilder->PrepareForThinLTO = false; PMBuilder->PerformThinLTO = false; @@ -272,7 +273,7 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM PMBuilder->populateModulePassManager(MPM); // Set output passes. - if (dest_bin) { + if (dest_bin && !lto) { if (target_machine->addPassesToEmitFile(MPM, *dest_bin, nullptr, CGFT_ObjectFile)) { *error_message = strdup("TargetMachine can't emit an object file"); return true; @@ -299,6 +300,9 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM return true; } } + if (dest_bin && lto) { + WriteBitcodeToFile(*module, *dest_bin); + } if (time_report) { TimerGroup::printAll(errs()); diff --git a/src/zig_llvm.h b/src/zig_llvm.h index 9a31092265..504ea4ec01 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -48,7 +48,7 @@ ZIG_EXTERN_C char *ZigLLVMGetNativeFeatures(void); ZIG_EXTERN_C bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMModuleRef module_ref, char **error_message, bool is_debug, - bool is_small, bool time_report, bool tsan, + bool is_small, bool time_report, bool tsan, bool lto, const char *asm_filename, const char *bin_filename, const char *llvm_ir_filename); diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index 189a2ed7fa..2aeee6845b 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -62,6 +62,14 @@ const known_options = [_]KnownOpt{ .name = "fno-PIE", .ident = "no_pie", }, + .{ + .name = "flto", + .ident = "lto", + }, + .{ + .name = "fno-lto", + .ident = "no_lto", + }, .{ .name = "nolibc", .ident = "nostdlib", -- cgit v1.2.3 From a0d81caec99fe0d1cd803b0ba461b6e02829b476 Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Sun, 24 Jan 2021 14:35:14 +0100 Subject: Nested conditions and loops support --- src/codegen/wasm.zig | 327 ++++++++++++++++++++++++++++++++++++++++----------- src/link/Wasm.zig | 5 +- 2 files changed, 263 insertions(+), 69 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index cbb2a42189..8acaebbd75 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -4,6 +4,7 @@ const ArrayList = std.ArrayList; const assert = std.debug.assert; const leb = std.leb; const mem = std.mem; +const wasm = std.wasm; const Module = @import("../Module.zig"); const Decl = Module.Decl; @@ -12,6 +13,7 @@ const Inst = ir.Inst; const Type = @import("../type.zig").Type; const Value = @import("../value.zig").Value; const Compilation = @import("../Compilation.zig"); +const AnyMCValue = @import("../codegen.zig").AnyMCValue; /// Wasm Value, created when generating an instruction const WValue = union(enum) { @@ -20,23 +22,14 @@ const WValue = union(enum) { local: u32, /// Instruction holding a constant `Value` constant: *Inst, - /// Block label + /// Offset position in the list of bytecode instructions + code_offset: usize, + /// The label of the block, used by breaks to find its relative distance block_idx: u32, }; /// Hashmap to store generated `WValue` for each `Inst` -pub const ValueTable = std.AutoHashMap(*Inst, WValue); - -/// Using a given `Type`, returns the corresponding wasm value type -fn genValtype(ty: Type) ?u8 { - return switch (ty.tag()) { - .f32 => 0x7D, - .f64 => 0x7C, - .u32, .i32 => 0x7F, - .u64, .i64 => 0x7E, - else => null, - }; -} +pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue); /// Code represents the `Code` section of wasm that /// belongs to a function @@ -58,13 +51,25 @@ pub const Context = struct { local_index: u32 = 0, /// If codegen fails, an error messages will be allocated and saved in `err_msg` err_msg: *Module.ErrorMsg, + /// Current block depth. Used to calculate the relative difference between a break + /// and block + block_depth: u32 = 0, + /// List of all locals' types generated throughout this declaration + /// used to emit locals count at start of 'code' section. + locals: std.ArrayListUnmanaged(u8), const InnerError = error{ OutOfMemory, CodegenFail, }; - /// Sets `err_msg` on `Context` and returns `error.CodegenFail` which is caught in link/Wasm.zig + pub fn deinit(self: *Context) void { + self.values.deinit(self.gpa); + self.locals.deinit(self.gpa); + self.* = undefined; + } + + /// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig fn fail(self: *Context, src: usize, comptime fmt: []const u8, args: anytype) InnerError { self.err_msg = try Module.ErrorMsg.create(self.gpa, .{ .file_scope = self.decl.getFileScope(), @@ -85,13 +90,35 @@ pub const Context = struct { return self.values.get(inst).?; // Instruction does not dominate all uses! } + /// Using a given `Type`, returns the corresponding wasm value type + fn genValtype(self: *Context, src: usize, ty: Type) InnerError!u8 { + return switch (ty.tag()) { + .f32 => wasm.valtype(.f32), + .f64 => wasm.valtype(.f64), + .u32, .i32 => wasm.valtype(.i32), + .u64, .i64 => wasm.valtype(.i64), + else => self.fail(src, "TODO - Wasm genValtype for type '{s}'", .{ty.tag()}), + }; + } + + /// Using a given `Type`, returns the corresponding wasm value type + /// Differently from `genValtype` this also allows `void` to create a block + /// with no return type + fn genBlockType(self: *Context, src: usize, ty: Type) InnerError!u8 { + return switch (ty.tag()) { + .void, .noreturn => wasm.block_empty, + else => self.genValtype(src, ty), + }; + } + /// Writes the bytecode depending on the given `WValue` in `val` fn emitWValue(self: *Context, val: WValue) InnerError!void { const writer = self.code.writer(); switch (val) { - .none, .block_idx => {}, + .block_idx => unreachable, + .none, .code_offset => {}, .local => |idx| { - try writer.writeByte(0x20); // local.get + try writer.writeByte(wasm.opcode(.local_get)); try leb.writeULEB128(writer, idx); }, .constant => |inst| try self.emitConstant(inst.castTag(.constant).?), // creates a new constant onto the stack @@ -102,8 +129,7 @@ pub const Context = struct { const ty = self.decl.typed_value.most_recent.typed_value.ty; const writer = self.func_type_data.writer(); - // functype magic - try writer.writeByte(0x60); + try writer.writeByte(wasm.function_type); // param types try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen())); @@ -112,8 +138,8 @@ pub const Context = struct { defer self.gpa.free(params); ty.fnParamTypes(params); for (params) |param_type| { - const val_type = genValtype(param_type) orelse - return self.fail(self.decl.src(), "TODO: Wasm codegen - arg type value for type '{s}'", .{param_type.tag()}); + // Can we maybe get the source index of each param? + const val_type = try self.genValtype(self.decl.src(), param_type); try writer.writeByte(val_type); } } @@ -124,8 +150,8 @@ pub const Context = struct { .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)), else => |ret_type| { try leb.writeULEB128(writer, @as(u32, 1)); - const val_type = genValtype(return_type) orelse - return self.fail(self.decl.src(), "TODO: Wasm codegen - return type value for type '{s}'", .{ret_type}); + // Can we maybe get the source index of the return type? + const val_type = try self.genValtype(self.decl.src(), return_type); try writer.writeByte(val_type); }, } @@ -140,37 +166,33 @@ pub const Context = struct { // Reserve space to write the size after generating the code try self.code.resize(5); + // offset into 'code' section where we will put our locals count + var local_offset = self.code.items.len; + // Write instructions // TODO: check for and handle death of instructions const tv = self.decl.typed_value.most_recent.typed_value; const mod_fn = tv.val.castTag(.function).?.data; + try self.genBody(mod_fn.body); - var locals = std.ArrayList(u8).init(self.gpa); - defer locals.deinit(); - - for (mod_fn.body.instructions) |inst| { - if (inst.tag != .alloc) continue; - - const alloc: *Inst.NoOp = inst.castTag(.alloc).?; - const elem_type = alloc.base.ty.elemType(); - - const wasm_type = genValtype(elem_type) orelse - return self.fail(inst.src, "TODO: Wasm codegen - valtype for type '{s}'", .{elem_type.tag()}); - - try locals.append(wasm_type); - } - - try leb.writeULEB128(writer, @intCast(u32, locals.items.len)); - - // emit the actual locals amount - for (locals.items) |local| { - try leb.writeULEB128(writer, @as(u32, 1)); - try leb.writeULEB128(writer, local); // valtype + // finally, write our local types at the 'offset' position + { + var totals_buffer: [5]u8 = undefined; + leb.writeUnsignedFixed(5, totals_buffer[0..5], @intCast(u32, self.locals.items.len)); + try self.code.insertSlice(local_offset, &totals_buffer); + local_offset += 5; + + // emit the actual locals amount + for (self.locals.items) |local| { + var buf: [6]u8 = undefined; + leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1)); + buf[5] = local; + try self.code.insertSlice(local_offset, &buf); + local_offset += 6; + } } - try self.genBody(mod_fn.body); - - try writer.writeByte(0x0B); // end + try writer.writeByte(wasm.opcode(.end)); // Fill in the size of the generated code to the reserved space at the // beginning of the buffer. @@ -183,10 +205,20 @@ pub const Context = struct { .add => self.genAdd(inst.castTag(.add).?), .alloc => self.genAlloc(inst.castTag(.alloc).?), .arg => self.genArg(inst.castTag(.arg).?), + .block => self.genBlock(inst.castTag(.block).?), + .br => self.genBr(inst.castTag(.br).?), .call => self.genCall(inst.castTag(.call).?), + .cmp_eq => self.genCmp(inst.castTag(.cmp_eq).?, .eq), + .cmp_gte => self.genCmp(inst.castTag(.cmp_gte).?, .gte), + .cmp_gt => self.genCmp(inst.castTag(.cmp_gt).?, .gt), + .cmp_lte => self.genCmp(inst.castTag(.cmp_lte).?, .lte), + .cmp_lt => self.genCmp(inst.castTag(.cmp_lt).?, .lt), + .cmp_neq => self.genCmp(inst.castTag(.cmp_neq).?, .neq), + .condbr => self.genCondBr(inst.castTag(.condbr).?), .constant => unreachable, .dbg_stmt => WValue.none, .load => self.genLoad(inst.castTag(.load).?), + .loop => self.genLoop(inst.castTag(.loop).?), .ret => self.genRet(inst.castTag(.ret).?), .retvoid => WValue.none, .store => self.genStore(inst.castTag(.store).?), @@ -197,7 +229,7 @@ pub const Context = struct { fn genBody(self: *Context, body: ir.Body) InnerError!void { for (body.instructions) |inst| { const result = try self.genInst(inst); - try self.values.putNoClobber(inst, result); + try self.values.putNoClobber(self.gpa, inst, result); } } @@ -205,7 +237,7 @@ pub const Context = struct { // TODO: Implement tail calls const operand = self.resolveInst(inst.operand); try self.emitWValue(operand); - return WValue.none; + return .none; } fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue { @@ -219,7 +251,7 @@ pub const Context = struct { try self.emitWValue(arg_val); } - try self.code.append(0x10); // call + try self.code.append(wasm.opcode(.call)); // The function index immediate argument will be filled in using this data // in link.Wasm.flush(). @@ -228,10 +260,14 @@ pub const Context = struct { .decl = target, }); - return WValue.none; + return .none; } fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue { + const elem_type = inst.base.ty.elemType(); + const valtype = try self.genValtype(inst.base.src, elem_type); + try self.locals.append(self.gpa, valtype); + defer self.local_index += 1; return WValue{ .local = self.local_index }; } @@ -243,15 +279,14 @@ pub const Context = struct { const rhs = self.resolveInst(inst.rhs); try self.emitWValue(rhs); - try writer.writeByte(0x21); // local.set + try writer.writeByte(wasm.opcode(.local_set)); try leb.writeULEB128(writer, lhs.local); - return WValue.none; + return .none; } fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue { const operand = self.resolveInst(inst.operand); - try self.emitWValue(operand); - return WValue.none; + return operand; } fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue { @@ -267,44 +302,44 @@ pub const Context = struct { try self.emitWValue(lhs); try self.emitWValue(rhs); - const opcode: u8 = switch (inst.base.ty.tag()) { - .u32, .i32 => 0x6A, //i32.add - .u64, .i64 => 0x7C, //i64.add - .f32 => 0x92, //f32.add - .f64 => 0xA0, //f64.add + const opcode: wasm.Opcode = switch (inst.base.ty.tag()) { + .u32, .i32 => .i32_add, + .u64, .i64 => .i64_add, + .f32 => .f32_add, + .f64 => .f64_add, else => return self.fail(inst.base.src, "TODO - Implement wasm genAdd for type '{s}'", .{inst.base.ty.tag()}), }; - try self.code.append(opcode); - return WValue.none; + try self.code.append(wasm.opcode(opcode)); + return .none; } fn emitConstant(self: *Context, inst: *Inst.Constant) InnerError!void { const writer = self.code.writer(); switch (inst.base.ty.tag()) { .u32 => { - try writer.writeByte(0x41); // i32.const + try writer.writeByte(wasm.opcode(.i32_const)); try leb.writeILEB128(writer, inst.val.toUnsignedInt()); }, .i32 => { - try writer.writeByte(0x41); // i32.const + try writer.writeByte(wasm.opcode(.i32_const)); try leb.writeILEB128(writer, inst.val.toSignedInt()); }, .u64 => { - try writer.writeByte(0x42); // i64.const + try writer.writeByte(wasm.opcode(.i64_const)); try leb.writeILEB128(writer, inst.val.toUnsignedInt()); }, .i64 => { - try writer.writeByte(0x42); // i64.const + try writer.writeByte(wasm.opcode(.i64_const)); try leb.writeILEB128(writer, inst.val.toSignedInt()); }, .f32 => { - try writer.writeByte(0x43); // f32.const + try writer.writeByte(wasm.opcode(.f32_const)); // TODO: enforce LE byte order try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32))); }, .f64 => { - try writer.writeByte(0x44); // f64.const + try writer.writeByte(wasm.opcode(.f64_const)); // TODO: enforce LE byte order try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64))); }, @@ -312,4 +347,162 @@ pub const Context = struct { else => |ty| return self.fail(inst.base.src, "Wasm TODO: emitConstant for type {s}", .{ty}), } } + + fn genBlock(self: *Context, block: *Inst.Block) InnerError!WValue { + const block_ty = try self.genBlockType(block.base.src, block.base.ty); + + block.codegen = .{ + // we don't use relocs, so using `relocs` is illegal behaviour. + .relocs = undefined, + // Here we set the current block idx, so conditions know the depth to jump + // to when breaking out. This will be set to .none when it is found again within + // the same block + .mcv = @bitCast(AnyMCValue, WValue{ .block_idx = self.block_depth }), + }; + self.block_depth += 1; + + try self.code.append(wasm.opcode(.block)); + try self.code.append(block_ty); + try self.genBody(block.body); + try self.code.append(wasm.opcode(.end)); + + self.block_depth -= 1; + return .none; + } + + fn genLoop(self: *Context, loop: *Inst.Loop) InnerError!WValue { + const loop_ty = try self.genBlockType(loop.base.src, loop.base.ty); + + try self.code.append(wasm.opcode(.loop)); + try self.code.append(loop_ty); + self.block_depth += 1; + try self.genBody(loop.body); + self.block_depth -= 1; + + try self.code.append(wasm.opcode(.end)); + + return .none; + } + + fn genCondBr(self: *Context, condbr: *Inst.CondBr) InnerError!WValue { + const condition = self.resolveInst(condbr.condition); + const writer = self.code.writer(); + + // insert blocks at the position of `offset` so + // the condition can jump to it + const offset = condition.code_offset; + try self.code.insert(offset, wasm.opcode(.block)); + try self.code.insert(offset, try self.genBlockType(condbr.base.src, condbr.base.ty)); + + // we inserted the block in front of the condition + // so now check if condition matches. If not, break outside this block + // and continue with the regular codepath + try writer.writeByte(wasm.opcode(.br_if)); + try leb.writeULEB128(writer, @as(u32, 0)); + + // else body in case condition does not match + try self.genBody(condbr.else_body); + + // finally, tell wasm we have reached the end of the block we inserted above + try writer.writeByte(wasm.opcode(.end)); + + // Outer block that matches the condition + try self.genBody(condbr.then_body); + + return .none; + } + + fn genCmp(self: *Context, inst: *Inst.BinOp, op: std.math.CompareOperator) InnerError!WValue { + const ty = inst.lhs.ty.tag(); + + // save offset, so potential conditions can insert blocks in front of + // the comparison that we can later jump back to + const offset = self.code.items.len - 1; + + const lhs = self.resolveInst(inst.lhs); + const rhs = self.resolveInst(inst.rhs); + + try self.emitWValue(lhs); + try self.emitWValue(rhs); + + const opcode_maybe: ?wasm.Opcode = switch (op) { + .lt => @as(?wasm.Opcode, switch (ty) { + .i32 => .i32_lt_s, + .u32 => .i32_lt_u, + .i64 => .i64_lt_s, + .u64 => .i64_lt_u, + .f32 => .f32_lt, + .f64 => .f64_lt, + else => null, + }), + .lte => @as(?wasm.Opcode, switch (ty) { + .i32 => .i32_le_s, + .u32 => .i32_le_u, + .i64 => .i64_le_s, + .u64 => .i64_le_u, + .f32 => .f32_le, + .f64 => .f64_le, + else => null, + }), + .eq => @as(?wasm.Opcode, switch (ty) { + .i32, .u32 => .i32_eq, + .i64, .u64 => .i64_eq, + .f32 => .f32_eq, + .f64 => .f64_eq, + else => null, + }), + .gte => @as(?wasm.Opcode, switch (ty) { + .i32 => .i32_ge_s, + .u32 => .i32_ge_u, + .i64 => .i64_ge_s, + .u64 => .i64_ge_u, + .f32 => .f32_ge, + .f64 => .f64_ge, + else => null, + }), + .gt => @as(?wasm.Opcode, switch (ty) { + .i32 => .i32_gt_s, + .u32 => .i32_gt_u, + .i64 => .i64_gt_s, + .u64 => .i64_gt_u, + .f32 => .f32_gt, + .f64 => .f64_gt, + else => null, + }), + .neq => @as(?wasm.Opcode, switch (ty) { + .i32, .u32 => .i32_ne, + .i64, .u64 => .i64_ne, + .f32 => .f32_ne, + .f64 => .f64_ne, + else => null, + }), + }; + + const opcode = opcode_maybe orelse + return self.fail(inst.base.src, "TODO - Wasm genCmp for type '{s}' and operator '{s}'", .{ ty, @tagName(op) }); + + try self.code.append(wasm.opcode(opcode)); + return WValue{ .code_offset = offset }; + } + + fn genBr(self: *Context, br: *Inst.Br) InnerError!WValue { + // of operand has codegen bits we should break with a value + if (br.operand.ty.hasCodeGenBits()) { + const operand = self.resolveInst(br.operand); + try self.emitWValue(operand); + } + + // if the block contains a block_idx, do a relative jump to it + // if `wvalue` was already 'consumed', simply break out of current block + const wvalue = @bitCast(WValue, br.block.codegen.mcv); + const idx: u32 = if (wvalue == .block_idx) blk: { + br.block.codegen.mcv = @bitCast(AnyMCValue, WValue{ .none = {} }); + break :blk self.block_depth - wvalue.block_idx; + } else 0; + + const writer = self.code.writer(); + try writer.writeByte(wasm.opcode(.br)); + try leb.writeULEB128(writer, idx); + return WValue.none; + } }; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index ec5ca0b9cf..1001e616e2 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -103,13 +103,14 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { var context = codegen.Context{ .gpa = self.base.allocator, - .values = codegen.ValueTable.init(self.base.allocator), + .values = .{}, .code = managed_code, .func_type_data = managed_functype, .decl = decl, .err_msg = undefined, + .locals = .{}, }; - defer context.values.deinit(); + defer context.deinit(); // generate the 'code' section for the function declaration context.gen() catch |err| switch (err) { -- cgit v1.2.3 From cc46c1b9024beefdd82ce8abd07e8849a72db20c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Tue, 26 Jan 2021 19:47:15 +0100 Subject: Add tests, fix locals that are created in blocks like loops, and handle all breaks correctly --- src/codegen/wasm.zig | 86 +++++++++++++++++++++++++----------------------- src/link/Wasm.zig | 9 ++++- test/stage2/wasm.zig | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 41 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 8acaebbd75..abc411b551 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -163,11 +163,8 @@ pub const Context = struct { try self.genFunctype(); const writer = self.code.writer(); - // Reserve space to write the size after generating the code - try self.code.resize(5); - - // offset into 'code' section where we will put our locals count - var local_offset = self.code.items.len; + // Reserve space to write the size after generating the code as well as space for locals count + try self.code.resize(10); // Write instructions // TODO: check for and handle death of instructions @@ -177,10 +174,10 @@ pub const Context = struct { // finally, write our local types at the 'offset' position { - var totals_buffer: [5]u8 = undefined; - leb.writeUnsignedFixed(5, totals_buffer[0..5], @intCast(u32, self.locals.items.len)); - try self.code.insertSlice(local_offset, &totals_buffer); - local_offset += 5; + leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len)); + + // offset into 'code' section where we will put our locals types + var local_offset: usize = 10; // emit the actual locals amount for (self.locals.items) |local| { @@ -285,8 +282,7 @@ pub const Context = struct { } fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue { - const operand = self.resolveInst(inst.operand); - return operand; + return self.resolveInst(inst.operand); } fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue { @@ -351,35 +347,49 @@ pub const Context = struct { fn genBlock(self: *Context, block: *Inst.Block) InnerError!WValue { const block_ty = try self.genBlockType(block.base.src, block.base.ty); + try self.startBlock(.block, block_ty, null); block.codegen = .{ // we don't use relocs, so using `relocs` is illegal behaviour. .relocs = undefined, - // Here we set the current block idx, so conditions know the depth to jump - // to when breaking out. This will be set to .none when it is found again within - // the same block + // Here we set the current block idx, so breaks know the depth to jump + // to when breaking out. .mcv = @bitCast(AnyMCValue, WValue{ .block_idx = self.block_depth }), }; + try self.genBody(block.body); + try self.endBlock(); + + return .none; + } + + /// appends a new wasm block to the code section and increases the `block_depth` by 1 + fn startBlock(self: *Context, block_type: wasm.Opcode, valtype: u8, with_offset: ?usize) !void { self.block_depth += 1; + if (with_offset) |offset| { + try self.code.insert(offset, wasm.opcode(block_type)); + try self.code.insert(offset + 1, valtype); + } else { + try self.code.append(wasm.opcode(block_type)); + try self.code.append(valtype); + } + } - try self.code.append(wasm.opcode(.block)); - try self.code.append(block_ty); - try self.genBody(block.body); + /// Ends the current wasm block and decreases the `block_depth` by 1 + fn endBlock(self: *Context) !void { try self.code.append(wasm.opcode(.end)); - self.block_depth -= 1; - return .none; } fn genLoop(self: *Context, loop: *Inst.Loop) InnerError!WValue { const loop_ty = try self.genBlockType(loop.base.src, loop.base.ty); - try self.code.append(wasm.opcode(.loop)); - try self.code.append(loop_ty); - self.block_depth += 1; + try self.startBlock(.loop, loop_ty, null); try self.genBody(loop.body); - self.block_depth -= 1; - try self.code.append(wasm.opcode(.end)); + // breaking to the index of a loop block will continue the loop instead + try self.code.append(wasm.opcode(.br)); + try leb.writeULEB128(self.code.writer(), @as(u32, 0)); + + try self.endBlock(); return .none; } @@ -388,23 +398,22 @@ pub const Context = struct { const condition = self.resolveInst(condbr.condition); const writer = self.code.writer(); + // TODO: Handle death instructions for then and else body + // insert blocks at the position of `offset` so // the condition can jump to it const offset = condition.code_offset; - try self.code.insert(offset, wasm.opcode(.block)); - try self.code.insert(offset, try self.genBlockType(condbr.base.src, condbr.base.ty)); + const block_ty = try self.genBlockType(condbr.base.src, condbr.base.ty); + try self.startBlock(.block, block_ty, offset); // we inserted the block in front of the condition // so now check if condition matches. If not, break outside this block - // and continue with the regular codepath + // and continue with the then codepath try writer.writeByte(wasm.opcode(.br_if)); try leb.writeULEB128(writer, @as(u32, 0)); - // else body in case condition does not match try self.genBody(condbr.else_body); - - // finally, tell wasm we have reached the end of the block we inserted above - try writer.writeByte(wasm.opcode(.end)); + try self.endBlock(); // Outer block that matches the condition try self.genBody(condbr.then_body); @@ -417,7 +426,7 @@ pub const Context = struct { // save offset, so potential conditions can insert blocks in front of // the comparison that we can later jump back to - const offset = self.code.items.len - 1; + const offset = self.code.items.len; const lhs = self.resolveInst(inst.lhs); const rhs = self.resolveInst(inst.rhs); @@ -492,17 +501,14 @@ pub const Context = struct { try self.emitWValue(operand); } - // if the block contains a block_idx, do a relative jump to it - // if `wvalue` was already 'consumed', simply break out of current block + // every block contains a `WValue` with its block index. + // We then determine how far we have to jump to it by substracting it from current block depth const wvalue = @bitCast(WValue, br.block.codegen.mcv); - const idx: u32 = if (wvalue == .block_idx) blk: { - br.block.codegen.mcv = @bitCast(AnyMCValue, WValue{ .none = {} }); - break :blk self.block_depth - wvalue.block_idx; - } else 0; - + const idx: u32 = self.block_depth - wvalue.block_idx; const writer = self.code.writer(); try writer.writeByte(wasm.opcode(.br)); try leb.writeULEB128(writer, idx); - return WValue.none; + + return .none; } }; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 1001e616e2..c39e995966 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -122,6 +122,13 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { else => |e| return err, }; + // as locals are patched afterwards, the offsets of funcidx's are off, + // here we update them to correct them + for (decl.fn_link.wasm.?.idx_refs.items) |*func| { + // For each local, add 6 bytes (count + type) + func.offset += @intCast(u32, context.locals.items.len * 6); + } + fn_data.functype = context.func_type_data.toUnmanaged(); fn_data.code = context.code.toUnmanaged(); } @@ -238,7 +245,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { try writer.writeAll(fn_data.code.items[current..idx_ref.offset]); current = idx_ref.offset; // Use a fixed width here to make calculating the code size - // in codegen.wasm.genCode() simpler. + // in codegen.wasm.gen() simpler. var buf: [5]u8 = undefined; leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?); try writer.writeAll(&buf); diff --git a/test/stage2/wasm.zig b/test/stage2/wasm.zig index f522db8809..06ede2d735 100644 --- a/test/stage2/wasm.zig +++ b/test/stage2/wasm.zig @@ -122,4 +122,96 @@ pub fn addCases(ctx: *TestContext) !void { \\} , "35\n"); } + + { + var case = ctx.exe("wasm conditions", wasi); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 5; + \\ if (i > @as(u32, 4)) { + \\ i += 10; + \\ } + \\ return i; + \\} + , "15\n"); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 5; + \\ if (i < @as(u32, 4)) { + \\ i += 10; + \\ } else { + \\ i = 2; + \\ } + \\ return i; + \\} + , "2\n"); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 5; + \\ if (i < @as(u32, 4)) { + \\ i += 10; + \\ } else if(i == @as(u32, 5)) { + \\ i = 20; + \\ } + \\ return i; + \\} + , "20\n"); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 11; + \\ if (i < @as(u32, 4)) { + \\ i += 10; + \\ } else { + \\ if (i > @as(u32, 10)) { + \\ i += 20; + \\ } else { + \\ i = 20; + \\ } + \\ } + \\ return i; + \\} + , "31\n"); + } + + { + var case = ctx.exe("wasm while loops", wasi); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 0; + \\ while(i < @as(u32, 5)){ + \\ i += 1; + \\ } + \\ + \\ return i; + \\} + , "5\n"); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 0; + \\ while(i < @as(u32, 10)){ + \\ var x: u32 = 1; + \\ i += x; + \\ } + \\ return i; + \\} + , "10\n"); + + case.addCompareOutput( + \\export fn _start() u32 { + \\ var i: u32 = 0; + \\ while(i < @as(u32, 10)){ + \\ var x: u32 = 1; + \\ i += x; + \\ if (i == @as(u32, 5)) break; + \\ } + \\ return i; + \\} + , "5\n"); + } } -- cgit v1.2.3 From 81c512f35b1926cf3fb6f29b97e68256aa164f68 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 26 Jan 2021 19:50:46 +0200 Subject: stage2 cbe: loop instruction --- src/codegen/c.zig | 99 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 41 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index b26f753757..ccde36a10d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -6,7 +6,8 @@ const Writer = std.ArrayList(u8).Writer; const link = @import("../link.zig"); const Module = @import("../Module.zig"); const Compilation = @import("../Compilation.zig"); -const Inst = @import("../ir.zig").Inst; +const ir = @import("../ir.zig"); +const Inst = ir.Inst; const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; const TypedValue = @import("../TypedValue.zig"); @@ -324,51 +325,13 @@ pub fn genDecl(o: *Object) !void { try fwd_decl_writer.writeAll(";\n"); const func: *Module.Fn = func_payload.data; - const instructions = func.body.instructions; const writer = o.code.writer(); try writer.writeAll("\n"); try o.dg.renderFunctionSignature(writer, is_global); - if (instructions.len == 0) { - try writer.writeAll(" {}\n"); - return; - } - - try writer.writeAll(" {"); + + try genBody(o, func.body); try writer.writeAll("\n"); - for (instructions) |inst| { - const result_value = switch (inst.tag) { - .add => try genBinOp(o, inst.castTag(.add).?, " + "), - .alloc => try genAlloc(o, inst.castTag(.alloc).?), - .arg => genArg(o), - .assembly => try genAsm(o, inst.castTag(.assembly).?), - .block => try genBlock(o, inst.castTag(.block).?), - .bitcast => try genBitcast(o, inst.castTag(.bitcast).?), - .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?), - .call => try genCall(o, inst.castTag(.call).?), - .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "), - .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "), - .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "), - .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "), - .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "), - .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "), - .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?), - .intcast => try genIntCast(o, inst.castTag(.intcast).?), - .load => try genLoad(o, inst.castTag(.load).?), - .ret => try genRet(o, inst.castTag(.ret).?), - .retvoid => try genRetVoid(o), - .store => try genStore(o, inst.castTag(.store).?), - .sub => try genBinOp(o, inst.castTag(.sub).?, " - "), - .unreach => try genUnreach(o, inst.castTag(.unreach).?), - else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), - }; - switch (result_value) { - .none => {}, - else => try o.value_map.putNoClobber(inst, result_value), - } - } - - try writer.writeAll("}\n"); } else if (tv.val.tag() == .extern_fn) { const writer = o.code.writer(); try writer.writeAll("ZIG_EXTERN_C "); @@ -410,6 +373,52 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { } } +pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!void { + const writer = o.code.writer(); + if (body.instructions.len == 0) { + try writer.writeAll(" {}"); + return; + } + + try writer.writeAll(" {"); + + try writer.writeAll("\n"); + for (body.instructions) |inst| { + const result_value = switch (inst.tag) { + .add => try genBinOp(o, inst.castTag(.add).?, " + "), + .alloc => try genAlloc(o, inst.castTag(.alloc).?), + .arg => genArg(o), + .assembly => try genAsm(o, inst.castTag(.assembly).?), + .block => try genBlock(o, inst.castTag(.block).?), + .bitcast => try genBitcast(o, inst.castTag(.bitcast).?), + .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?), + .call => try genCall(o, inst.castTag(.call).?), + .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "), + .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "), + .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "), + .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "), + .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "), + .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "), + .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?), + .intcast => try genIntCast(o, inst.castTag(.intcast).?), + .load => try genLoad(o, inst.castTag(.load).?), + .ret => try genRet(o, inst.castTag(.ret).?), + .retvoid => try genRetVoid(o), + .store => try genStore(o, inst.castTag(.store).?), + .sub => try genBinOp(o, inst.castTag(.sub).?, " - "), + .unreach => try genUnreach(o, inst.castTag(.unreach).?), + .loop => try genLoop(o, inst.castTag(.loop).?), + else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), + }; + switch (result_value) { + .none => {}, + else => try o.value_map.putNoClobber(inst, result_value), + } + } + + try writer.writeAll("}"); +} + fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue { const writer = o.code.writer(); @@ -627,6 +636,14 @@ fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { return CValue.none; } +fn genLoop(o: *Object, inst: *Inst.Loop) !CValue { + try o.indent(); + try o.code.writer().writeAll("while (true)"); + try genBody(o, inst.body); + try o.code.writer().writeAll("\n"); + return CValue.none; +} + fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (as.base.isUnused() and !as.is_volatile) return CValue.none; -- cgit v1.2.3 From 6ca0ff90b63cea79d8d63519a3c133cfde111884 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 26 Jan 2021 20:25:30 +0200 Subject: stage2 cbe: use AutoIndentingStream --- src/codegen/c.zig | 100 ++++++++++++++++++++++-------------------------------- src/link/C.zig | 2 ++ 2 files changed, 43 insertions(+), 59 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index ccde36a10d..9bccde5ffd 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1,7 +1,6 @@ const std = @import("std"); const mem = std.mem; const log = std.log.scoped(.c); -const Writer = std.ArrayList(u8).Writer; const link = @import("../link.zig"); const Module = @import("../Module.zig"); @@ -42,6 +41,7 @@ pub const Object = struct { value_map: CValueMap, next_arg_index: usize = 0, next_local_index: usize = 0, + indent_writer: std.io.AutoIndentingStream(std.ArrayList(u8).Writer), fn resolveInst(o: *Object, inst: *Inst) !CValue { if (inst.value()) |_| { @@ -58,31 +58,28 @@ pub const Object = struct { fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue { const local_value = o.allocLocalValue(); - try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability); + try o.renderTypeAndName(o.writer(), ty, local_value, mutability); return local_value; } - fn indent(o: *Object) !void { - const indent_size = 4; - const indent_level = 1; - const indent_amt = indent_size * indent_level; - try o.code.writer().writeByteNTimes(' ', indent_amt); + fn writer(o: *Object) std.io.AutoIndentingStream(std.ArrayList(u8).Writer).Writer { + return o.indent_writer.writer(); } - fn writeCValue(o: *Object, writer: Writer, c_value: CValue) !void { + fn writeCValue(o: *Object, w: anytype, c_value: CValue) !void { switch (c_value) { .none => unreachable, - .local => |i| return writer.print("t{d}", .{i}), - .local_ref => |i| return writer.print("&t{d}", .{i}), - .constant => |inst| return o.dg.renderValue(writer, inst.ty, inst.value().?), - .arg => |i| return writer.print("a{d}", .{i}), - .decl => |decl| return writer.writeAll(mem.span(decl.name)), + .local => |i| return w.print("t{d}", .{i}), + .local_ref => |i| return w.print("&t{d}", .{i}), + .constant => |inst| return o.dg.renderValue(w, inst.ty, inst.value().?), + .arg => |i| return w.print("a{d}", .{i}), + .decl => |decl| return w.writeAll(mem.span(decl.name)), } } fn renderTypeAndName( o: *Object, - writer: Writer, + w: anytype, ty: Type, name: CValue, mutability: Mutability, @@ -98,15 +95,15 @@ pub const Object = struct { render_ty = render_ty.elemType(); } - try o.dg.renderType(writer, render_ty); + try o.dg.renderType(w, render_ty); const const_prefix = switch (mutability) { .Const => "const ", .Mut => "", }; - try writer.print(" {s}", .{const_prefix}); - try o.writeCValue(writer, name); - try writer.writeAll(suffix.items); + try w.print(" {s}", .{const_prefix}); + try o.writeCValue(w, name); + try w.writeAll(suffix.items); } }; @@ -127,7 +124,7 @@ pub const DeclGen = struct { fn renderValue( dg: *DeclGen, - writer: Writer, + writer: anytype, t: Type, val: Value, ) error{ OutOfMemory, AnalysisFail }!void { @@ -204,7 +201,7 @@ pub const DeclGen = struct { } } - fn renderFunctionSignature(dg: *DeclGen, w: Writer, is_global: bool) !void { + fn renderFunctionSignature(dg: *DeclGen, w: anytype, is_global: bool) !void { if (!is_global) { try w.writeAll("static "); } @@ -228,7 +225,7 @@ pub const DeclGen = struct { try w.writeByte(')'); } - fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void { + fn renderType(dg: *DeclGen, w: anytype, t: Type) error{ OutOfMemory, AnalysisFail }!void { switch (t.zigTypeTag()) { .NoReturn => { try w.writeAll("zig_noreturn void"); @@ -325,20 +322,19 @@ pub fn genDecl(o: *Object) !void { try fwd_decl_writer.writeAll(";\n"); const func: *Module.Fn = func_payload.data; - const writer = o.code.writer(); - try writer.writeAll("\n"); - try o.dg.renderFunctionSignature(writer, is_global); + try o.indent_writer.insertNewline(); + try o.dg.renderFunctionSignature(o.writer(), is_global); try genBody(o, func.body); - try writer.writeAll("\n"); + try o.indent_writer.insertNewline(); } else if (tv.val.tag() == .extern_fn) { - const writer = o.code.writer(); + const writer = o.writer(); try writer.writeAll("ZIG_EXTERN_C "); try o.dg.renderFunctionSignature(writer, true); try writer.writeAll(";\n"); } else { - const writer = o.code.writer(); + const writer = o.writer(); try writer.writeAll("static "); // TODO ask the Decl if it is const @@ -374,15 +370,15 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { } pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!void { - const writer = o.code.writer(); + const writer = o.writer(); if (body.instructions.len == 0) { try writer.writeAll(" {}"); return; } - try writer.writeAll(" {"); + try writer.writeAll(" {\n"); + o.indent_writer.pushIndent(); - try writer.writeAll("\n"); for (body.instructions) |inst| { const result_value = switch (inst.tag) { .add => try genBinOp(o, inst.castTag(.add).?, " + "), @@ -416,14 +412,14 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi } } + o.indent_writer.popIndent(); try writer.writeAll("}"); } fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue { - const writer = o.code.writer(); + const writer = o.writer(); // First line: the variable used as data storage. - try o.indent(); const elem_type = alloc.base.ty.elemType(); const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut; const local = try o.allocLocal(elem_type, mutability); @@ -439,15 +435,13 @@ fn genArg(o: *Object) CValue { } fn genRetVoid(o: *Object) !CValue { - try o.indent(); - try o.code.writer().print("return;\n", .{}); + try o.writer().print("return;\n", .{}); return CValue.none; } fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { const operand = try o.resolveInst(inst.operand); - const writer = o.code.writer(); - try o.indent(); + const writer = o.writer(); const local = try o.allocLocal(inst.base.ty, .Const); switch (operand) { .local_ref => |i| { @@ -467,8 +461,7 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { fn genRet(o: *Object, inst: *Inst.UnOp) !CValue { const operand = try o.resolveInst(inst.operand); - try o.indent(); - const writer = o.code.writer(); + const writer = o.writer(); try writer.writeAll("return "); try o.writeCValue(writer, operand); try writer.writeAll(";\n"); @@ -481,8 +474,7 @@ fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue { const from = try o.resolveInst(inst.operand); - try o.indent(); - const writer = o.code.writer(); + const writer = o.writer(); const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); try o.dg.renderType(writer, inst.base.ty); @@ -497,8 +489,7 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue { const dest_ptr = try o.resolveInst(inst.lhs); const src_val = try o.resolveInst(inst.rhs); - try o.indent(); - const writer = o.code.writer(); + const writer = o.writer(); switch (dest_ptr) { .local_ref => |i| { const dest: CValue = .{ .local = i }; @@ -525,8 +516,7 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { const lhs = try o.resolveInst(inst.lhs); const rhs = try o.resolveInst(inst.rhs); - try o.indent(); - const writer = o.code.writer(); + const writer = o.writer(); const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = "); @@ -552,8 +542,7 @@ fn genCall(o: *Object, inst: *Inst.Call) !CValue { const unused_result = inst.base.isUnused(); var result_local: CValue = .none; - try o.indent(); - const writer = o.code.writer(); + const writer = o.writer(); if (unused_result) { if (ret_ty.hasCodeGenBits()) { try writer.print("(void)", .{}); @@ -596,8 +585,7 @@ fn genBlock(o: *Object, inst: *Inst.Block) !CValue { fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { const operand = try o.resolveInst(inst.operand); - const writer = o.code.writer(); - try o.indent(); + const writer = o.writer(); if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); @@ -611,7 +599,6 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { const local = try o.allocLocal(inst.base.ty, .Mut); try writer.writeAll(";\n"); - try o.indent(); try writer.writeAll("memcpy(&"); try o.writeCValue(writer, local); @@ -625,22 +612,19 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { } fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue { - try o.indent(); - try o.code.writer().writeAll("zig_breakpoint();\n"); + try o.writer().writeAll("zig_breakpoint();\n"); return CValue.none; } fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { - try o.indent(); - try o.code.writer().writeAll("zig_unreachable();\n"); + try o.writer().writeAll("zig_unreachable();\n"); return CValue.none; } fn genLoop(o: *Object, inst: *Inst.Loop) !CValue { - try o.indent(); - try o.code.writer().writeAll("while (true)"); + try o.writer().writeAll("while (true)"); try genBody(o, inst.body); - try o.code.writer().writeAll("\n"); + try o.indent_writer.insertNewline(); return CValue.none; } @@ -648,13 +632,12 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (as.base.isUnused() and !as.is_volatile) return CValue.none; - const writer = o.code.writer(); + const writer = o.writer(); for (as.inputs) |i, index| { if (i[0] == '{' and i[i.len - 1] == '}') { const reg = i[1 .. i.len - 1]; const arg = as.args[index]; const arg_c_value = try o.resolveInst(arg); - try o.indent(); try writer.writeAll("register "); try o.dg.renderType(writer, arg.ty); @@ -665,7 +648,6 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{}); } } - try o.indent(); const volatile_string: []const u8 = if (as.is_volatile) "volatile " else ""; try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source }); if (as.output) |_| { diff --git a/src/link/C.zig b/src/link/C.zig index a60d0efd8e..765249cd7d 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -95,7 +95,9 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { .gpa = module.gpa, .code = code.toManaged(module.gpa), .value_map = codegen.CValueMap.init(module.gpa), + .indent_writer = undefined, // set later so we can get a pointer to object.code }; + object.indent_writer = std.io.autoIndentingStream(4, object.code.writer()); defer object.value_map.deinit(); defer object.code.deinit(); defer object.dg.fwd_decl.deinit(); -- cgit v1.2.3 From bdfe3aeab8310a64cc9c2f5fac194a609aa0f13d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 26 Jan 2021 21:09:40 +0200 Subject: stage2 cbe: condbr and breaks --- src/codegen/c.zig | 56 +++++++++++++++++++++++++++++++++++++++++++++++------ test/stage2/cbe.zig | 18 +++++++++++++++++ 2 files changed, 68 insertions(+), 6 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 9bccde5ffd..bf6a5aac1f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -41,6 +41,7 @@ pub const Object = struct { value_map: CValueMap, next_arg_index: usize = 0, next_local_index: usize = 0, + next_block_index: usize = 0, indent_writer: std.io.AutoIndentingStream(std.ArrayList(u8).Writer), fn resolveInst(o: *Object, inst: *Inst) !CValue { @@ -255,8 +256,8 @@ pub const DeclGen = struct { .int_signed, .int_unsigned => { const info = t.intInfo(dg.module.getTarget()); const sign_prefix = switch (info.signedness) { - .signed => "i", - .unsigned => "", + .signed => "", + .unsigned => "u", }; inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { if (info.bits <= nbits) { @@ -325,6 +326,7 @@ pub fn genDecl(o: *Object) !void { try o.indent_writer.insertNewline(); try o.dg.renderFunctionSignature(o.writer(), is_global); + try o.writer().writeByte(' '); try genBody(o, func.body); try o.indent_writer.insertNewline(); @@ -372,11 +374,11 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!void { const writer = o.writer(); if (body.instructions.len == 0) { - try writer.writeAll(" {}"); + try writer.writeAll("{}"); return; } - try writer.writeAll(" {\n"); + try writer.writeAll("{\n"); o.indent_writer.pushIndent(); for (body.instructions) |inst| { @@ -404,6 +406,9 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .sub => try genBinOp(o, inst.castTag(.sub).?, " - "), .unreach => try genUnreach(o, inst.castTag(.unreach).?), .loop => try genLoop(o, inst.castTag(.loop).?), + .condbr => try genCondBr(o, inst.castTag(.condbr).?), + .br => try genBr(o, inst.castTag(.br).?), + .brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { @@ -579,7 +584,31 @@ fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue { } fn genBlock(o: *Object, inst: *Inst.Block) !CValue { - return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{}); + const block_id: usize = o.next_block_index; + o.next_block_index += 1; + // abuse codegen.msv to store the block's id + inst.codegen.mcv.a = block_id; + try genBody(o, inst.body); + try o.indent_writer.insertNewline(); + // label must be followed by an expression, add an empty one. + try o.writer().print("zig_block_{d}:;\n", .{block_id}); + + // blocks in C cannot result in values + // TODO we need some other way to pass the result of the block + return CValue.none; +} + +fn genBr(o: *Object, inst: *Inst.Br) !CValue { + if (inst.operand.ty.tag() != .void) { + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement block return values", .{}); + } + + return genBrVoid(o, inst.block); +} + +fn genBrVoid(o: *Object, block: *Inst.Block) !CValue { + try o.writer().print("goto zig_block_{d};\n", .{block.codegen.mcv.a}); + return CValue.none; } fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { @@ -622,12 +651,27 @@ fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { } fn genLoop(o: *Object, inst: *Inst.Loop) !CValue { - try o.writer().writeAll("while (true)"); + try o.writer().writeAll("while (true) "); try genBody(o, inst.body); try o.indent_writer.insertNewline(); return CValue.none; } +fn genCondBr(o: *Object, inst: *Inst.CondBr) !CValue { + const cond = try o.resolveInst(inst.condition); + const writer = o.writer(); + + try writer.writeAll("if ("); + try o.writeCValue(writer, cond); + try writer.writeAll(") "); + try genBody(o, inst.then_body); + try writer.writeAll(" else "); + try genBody(o, inst.else_body); + try o.indent_writer.insertNewline(); + + return CValue.none; +} + fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (as.base.isUnused() and !as.is_volatile) return CValue.none; diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 6d4e2062bf..c953d6077e 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -133,6 +133,24 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , ""); + + // Simple while loop + case.addCompareOutput( + \\export fn main() c_int { + \\ var a: c_int = 0; + \\ while (a < 5) : (a+=1) {} + \\ exit(a - 5); + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); } { -- cgit v1.2.3 From 258f3ec5ecf8d2a165382d5837bed0dac2e0375b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 27 Jan 2021 11:05:22 +0200 Subject: stage2 cbe: block results --- src/codegen/c.zig | 38 +++++++++++++++++++++++++++----------- test/stage2/cbe.zig | 21 +++++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index bf6a5aac1f..e33f812f0b 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -325,7 +325,7 @@ pub fn genDecl(o: *Object) !void { const func: *Module.Fn = func_payload.data; try o.indent_writer.insertNewline(); try o.dg.renderFunctionSignature(o.writer(), is_global); - + try o.writer().writeByte(' '); try genBody(o, func.body); @@ -586,28 +586,44 @@ fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue { fn genBlock(o: *Object, inst: *Inst.Block) !CValue { const block_id: usize = o.next_block_index; o.next_block_index += 1; - // abuse codegen.msv to store the block's id - inst.codegen.mcv.a = block_id; + const writer = o.writer(); + + // store the block id in relocs.capacity as it is not used for anything else in the C backend. + inst.codegen.relocs.capacity = block_id; + const result = if (inst.base.ty.tag() != .void and !inst.base.isUnused()) blk: { + // allocate a location for the result + const local = try o.allocLocal(inst.base.ty, .Mut); + try writer.writeAll(";\n"); + break :blk local; + } else + CValue{ .none = {} }; + + inst.codegen.mcv = @bitCast(@import("../codegen.zig").AnyMCValue, result); try genBody(o, inst.body); try o.indent_writer.insertNewline(); // label must be followed by an expression, add an empty one. - try o.writer().print("zig_block_{d}:;\n", .{block_id}); - - // blocks in C cannot result in values - // TODO we need some other way to pass the result of the block - return CValue.none; + try writer.print("zig_block_{d}:;\n", .{block_id}); + return result; } fn genBr(o: *Object, inst: *Inst.Br) !CValue { - if (inst.operand.ty.tag() != .void) { - return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement block return values", .{}); + const result = @bitCast(CValue, inst.block.codegen.mcv); + const writer = o.writer(); + + // If result is .none then the value of the block is unused. + if (inst.operand.ty.tag() != .void and result != .none) { + const operand = try o.resolveInst(inst.operand); + try o.writeCValue(writer, result); + try writer.writeAll(" = "); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); } return genBrVoid(o, inst.block); } fn genBrVoid(o: *Object, block: *Inst.Block) !CValue { - try o.writer().print("goto zig_block_{d};\n", .{block.codegen.mcv.a}); + try o.writer().print("goto zig_block_{d};\n", .{block.codegen.relocs.capacity}); return CValue.none; } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index c953d6077e..8a264f5ca6 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -151,6 +151,27 @@ pub fn addCases(ctx: *TestContext) !void { \\ unreachable; \\} , ""); + + // If expression + case.addCompareOutput( + \\export fn main() c_int { + \\ var cond: c_int = 0; + \\ var a: c_int = @as(c_int, if (cond == 0) + \\ 2 + \\ else + \\ 3) + 9; + \\ exit(a - 11); + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); } { -- cgit v1.2.3 From 106520329e5adc6cf5ef83595da6c9d5dd3c4b35 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 27 Jan 2021 11:40:34 +0200 Subject: stage2 cbe: implement switchbr --- src/Module.zig | 4 +-- src/codegen/c.zig | 35 ++++++++++++++++++++++++ src/ir.zig | 4 +-- test/stage2/cbe.zig | 78 ++++++++++++++++++++++++++--------------------------- 4 files changed, 78 insertions(+), 43 deletions(-) (limited to 'src/codegen') diff --git a/src/Module.zig b/src/Module.zig index b495afb336..46c3d513f1 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2215,7 +2215,7 @@ pub fn addSwitchBr( self: *Module, block: *Scope.Block, src: usize, - target_ptr: *Inst, + target: *Inst, cases: []Inst.SwitchBr.Case, else_body: ir.Body, ) !*Inst { @@ -2226,7 +2226,7 @@ pub fn addSwitchBr( .ty = Type.initTag(.noreturn), .src = src, }, - .target_ptr = target_ptr, + .target = target, .cases = cases, .else_body = else_body, }; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index e33f812f0b..7fcbe44205 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -129,6 +129,9 @@ pub const DeclGen = struct { t: Type, val: Value, ) error{ OutOfMemory, AnalysisFail }!void { + if (val.isUndef()) { + return dg.fail(dg.decl.src(), "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{}); + } switch (t.zigTypeTag()) { .Int => { if (t.isSignedInt()) @@ -196,6 +199,7 @@ pub const DeclGen = struct { }, } }, + .Bool => return writer.print("{}", .{val.toBool()}), else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ @tagName(e), }), @@ -409,6 +413,10 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .condbr => try genCondBr(o, inst.castTag(.condbr).?), .br => try genBr(o, inst.castTag(.br).?), .brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block), + .switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?), + // booland and boolor are non-short-circuit operations + .booland => try genBinOp(o, inst.castTag(.booland).?, " & "), + .boolor => try genBinOp(o, inst.castTag(.boolor).?, " | "), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { @@ -688,6 +696,33 @@ fn genCondBr(o: *Object, inst: *Inst.CondBr) !CValue { return CValue.none; } +fn genSwitchBr(o: *Object, inst: *Inst.SwitchBr) !CValue { + const target = try o.resolveInst(inst.target); + const writer = o.writer(); + + try writer.writeAll("switch ("); + try o.writeCValue(writer, target); + try writer.writeAll(") {\n"); + o.indent_writer.pushIndent(); + + for (inst.cases) |case| { + try writer.writeAll("case "); + try o.dg.renderValue(writer, inst.target.ty, case.item); + try writer.writeAll(": "); + // the case body must be noreturn so we don't need to insert a break + try genBody(o, case.body); + try o.indent_writer.insertNewline(); + } + + try writer.writeAll("default: "); + try genBody(o, inst.else_body); + try o.indent_writer.insertNewline(); + + o.indent_writer.popIndent(); + try writer.writeAll("}\n"); + return CValue.none; +} + fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (as.base.isUnused() and !as.is_volatile) return CValue.none; diff --git a/src/ir.zig b/src/ir.zig index 408efc3bba..0e83dbfd56 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -521,7 +521,7 @@ pub const Inst = struct { pub const base_tag = Tag.switchbr; base: Inst, - target_ptr: *Inst, + target: *Inst, cases: []Case, /// Set of instructions whose lifetimes end at the start of one of the cases. /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... ]. @@ -544,7 +544,7 @@ pub const Inst = struct { var i = index; if (i < 1) - return self.target_ptr; + return self.target; i -= 1; return null; diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 8a264f5ca6..aacb2b7077 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -133,45 +133,6 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ , ""); - - // Simple while loop - case.addCompareOutput( - \\export fn main() c_int { - \\ var a: c_int = 0; - \\ while (a < 5) : (a+=1) {} - \\ exit(a - 5); - \\} - \\ - \\fn exit(code: usize) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - , ""); - - // If expression - case.addCompareOutput( - \\export fn main() c_int { - \\ var cond: c_int = 0; - \\ var a: c_int = @as(c_int, if (cond == 0) - \\ 2 - \\ else - \\ 3) + 9; - \\ exit(a - 11); - \\} - \\ - \\fn exit(code: usize) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - , ""); } { @@ -224,6 +185,45 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + { + var case = ctx.exeFromCompiledC("control flow", .{}); + + // Simple while loop + case.addCompareOutput( + \\export fn main() c_int { + \\ var a: c_int = 0; + \\ while (a < 5) : (a+=1) {} + \\ return a - 5; + \\} + , ""); + + // If expression + case.addCompareOutput( + \\export fn main() c_int { + \\ var cond: c_int = 0; + \\ var a: c_int = @as(c_int, if (cond == 0) + \\ 2 + \\ else + \\ 3) + 9; + \\ return a - 11; + \\} + , ""); + + // Switch expression + case.addCompareOutput( + \\export fn main() c_int { + \\ var cond: c_int = 0; + \\ var a: c_int = switch (cond) { + \\ 1 => 1, + \\ 2 => 2, + \\ 99...300, 12 => 3, + \\ 0 => 4, + \\ else => 5, + \\ }; + \\ return a - 4; + \\} + , ""); + } ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable; -- cgit v1.2.3 From 3ec5c9a3bcae09c01cbe4f0505e6ab03834bbb98 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 27 Jan 2021 12:22:38 +0200 Subject: stage2 cbe: implement not and some bitwise ops --- src/codegen/c.zig | 24 ++++++++++++++++++++++-- test/stage2/cbe.zig | 7 +++++++ 2 files changed, 29 insertions(+), 2 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 7fcbe44205..39fa80ea3d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -293,6 +293,7 @@ pub const DeclGen = struct { try dg.renderType(w, t.elemType()); try w.writeAll(" *"); }, + .Null, .Undefined => unreachable, // must be const or comptime else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ @tagName(e), }), @@ -387,6 +388,7 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi for (body.instructions) |inst| { const result_value = switch (inst.tag) { + .constant => unreachable, // excluded from function bodies .add => try genBinOp(o, inst.castTag(.add).?, " + "), .alloc => try genAlloc(o, inst.castTag(.alloc).?), .arg => genArg(o), @@ -415,8 +417,10 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block), .switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?), // booland and boolor are non-short-circuit operations - .booland => try genBinOp(o, inst.castTag(.booland).?, " & "), - .boolor => try genBinOp(o, inst.castTag(.boolor).?, " | "), + .booland, .bitand => try genBinOp(o, inst.castTag(.booland).?, " & "), + .boolor, .bitor => try genBinOp(o, inst.castTag(.boolor).?, " | "), + .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "), + .not => try genUnOp(o, inst.castTag(.not).?, "!"), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { @@ -541,6 +545,22 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { return local; } +fn genUnOp(o: *Object, inst: *Inst.UnOp, operator: []const u8) !CValue { + if (inst.base.isUnused()) + return CValue.none; + + const operand = try o.resolveInst(inst.operand); + + const writer = o.writer(); + const local = try o.allocLocal(inst.base.ty, .Const); + + try writer.print(" = {s}", .{operator}); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); + + return local; +} + fn genCall(o: *Object, inst: *Inst.Call) !CValue { if (inst.func.castTag(.constant)) |func_inst| { const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn| diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index aacb2b7077..0eb2cf68b4 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -196,6 +196,13 @@ pub fn addCases(ctx: *TestContext) !void { \\ return a - 5; \\} , ""); + case.addCompareOutput( + \\export fn main() c_int { + \\ var a = true; + \\ while (!a) {} + \\ return 0; + \\} + , ""); // If expression case.addCompareOutput( -- cgit v1.2.3 From 75acfcf0eaa306b3a8872e50cb735e1d5eb18c52 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 1 Feb 2021 15:45:11 +0200 Subject: stage2: reimplement switch --- src/astgen.zig | 313 +++++++++++++++++++++++++++++++++++++++++++++++++++++- src/codegen/c.zig | 10 +- src/zir.zig | 60 +++++++++++ src/zir_sema.zig | 228 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 606 insertions(+), 5 deletions(-) (limited to 'src/codegen') diff --git a/src/astgen.zig b/src/astgen.zig index dfc5f06ddc..ece16d70da 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -309,7 +309,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?), .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?), - .Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}), + .Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?), .ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?), .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), @@ -2246,6 +2246,317 @@ fn forExpr( ); } +fn switchCaseUsesRef(node: *ast.Node.Switch) bool { + for (node.cases()) |uncasted_case| { + const case = uncasted_case.castTag(.SwitchCase).?; + const uncasted_payload = case.payload orelse continue; + const payload = uncasted_payload.castTag(.PointerPayload).?; + if (payload.ptr_token) |_| return true; + } + return false; +} + +fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp { + var cur = node; + while (true) { + switch (cur.tag) { + .Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur), + .GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr, + else => return null, + } + } +} + +fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst { + const tree = scope.tree(); + const switch_src = tree.token_locs[switch_node.switch_token].start; + const use_ref = switchCaseUsesRef(switch_node); + + var block_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = scope.ownerDecl().?, + .arena = scope.arena(), + .force_comptime = scope.isComptime(), + .instructions = .{}, + }; + setBlockResultLoc(&block_scope, rl); + defer block_scope.instructions.deinit(mod.gpa); + + var items = std.ArrayList(*zir.Inst).init(mod.gpa); + defer items.deinit(); + + // first we gather all the switch items and check else/'_' prongs + var else_src: ?usize = null; + var underscore_src: ?usize = null; + var first_range: ?*zir.Inst = null; + var simple_case_count: usize = 0; + for (switch_node.cases()) |uncasted_case| { + const case = uncasted_case.castTag(.SwitchCase).?; + const case_src = tree.token_locs[case.firstToken()].start; + assert(case.items_len != 0); + + // Check for else/_ prong, those are handled last. + if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { + if (else_src) |src| { + const msg = msg: { + const msg = try mod.errMsg( + scope, + case_src, + "multiple else prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous else prong is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + else_src = case_src; + continue; + } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and + mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) + { + if (underscore_src) |src| { + const msg = msg: { + const msg = try mod.errMsg( + scope, + case_src, + "multiple '_' prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + underscore_src = case_src; + continue; + } + + if (else_src) |some_else| { + if (underscore_src) |some_underscore| { + const msg = msg: { + const msg = try mod.errMsg( + scope, + switch_src, + "else and '_' prong in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, some_else, msg, "else prong is here", .{}); + try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + } + + if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) simple_case_count += 1; + + // generate all the switch items as comptime expressions + for (case.items()) |item| { + if (getRangeNode(item)) |range| { + const start = try comptimeExpr(mod, &block_scope.base, .none, range.lhs); + const end = try comptimeExpr(mod, &block_scope.base, .none, range.rhs); + const range_src = tree.token_locs[range.op_token].start; + const range_inst = try addZIRBinOp(mod, &block_scope.base, range_src, .switch_range, start, end); + try items.append(range_inst); + } else { + const item_inst = try comptimeExpr(mod, &block_scope.base, .none, item); + try items.append(item_inst); + } + } + } + + var special_prong: zir.Inst.SwitchBr.SpecialProng = .none; + if (else_src != null) special_prong = .@"else"; + if (underscore_src != null) special_prong = .underscore; + var cases = try block_scope.arena.alloc(zir.Inst.SwitchBr.Case, simple_case_count); + + const target_ptr = if (use_ref) try expr(mod, &block_scope.base, .ref, switch_node.expr) else null; + const target = if (target_ptr) |some| + try addZIRUnOp(mod, &block_scope.base, some.src, .deref, some) + else + try expr(mod, &block_scope.base, .none, switch_node.expr); + const switch_inst = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{ + .target = target, + .cases = cases, + .items = try block_scope.arena.dupe(*zir.Inst, items.items), + .else_body = undefined, // populated below + }, .{ + .range = first_range, + .special_prong = special_prong, + }); + + const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), + }); + + var case_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = block_scope.decl, + .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, + .instructions = .{}, + }; + defer case_scope.instructions.deinit(mod.gpa); + + var else_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = case_scope.decl, + .arena = case_scope.arena, + .force_comptime = case_scope.force_comptime, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + + // Now generate all but the special cases + var special_case: ?*ast.Node.SwitchCase = null; + var items_index: usize = 0; + var case_index: usize = 0; + for (switch_node.cases()) |uncasted_case| { + const case = uncasted_case.castTag(.SwitchCase).?; + const case_src = tree.token_locs[case.firstToken()].start; + // reset without freeing to reduce allocations. + case_scope.instructions.items.len = 0; + + // Check for else/_ prong, those are handled last. + if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { + special_case = case; + continue; + } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and + mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) + { + special_case = case; + continue; + } + + // If this is a simple one item prong then it is handled by the switchbr. + if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) { + const item = items.items[items_index]; + items_index += 1; + try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target, target_ptr); + + cases[case_index] = .{ + .item = item, + .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) }, + }; + case_index += 1; + continue; + } + + // TODO if the case has few items and no ranges it might be better + // to just handle them as switch prongs. + + // Check if the target matches any of the items. + // 1, 2, 3..6 will result in + // target == 1 or target == 2 or (target >= 3 and target <= 6) + var any_ok: ?*zir.Inst = null; + for (case.items()) |item| { + if (getRangeNode(item)) |range| { + const range_src = tree.token_locs[range.op_token].start; + const range_inst = items.items[items_index].castTag(.switch_range).?; + items_index += 1; + + // target >= start and target <= end + const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, range_inst.positionals.lhs); + const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, range_inst.positionals.rhs); + const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok); + + if (any_ok) |some| { + any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok); + } else { + any_ok = range_ok; + } + continue; + } + + const item_inst = items.items[items_index]; + items_index += 1; + const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst); + + if (any_ok) |some| { + any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok); + } else { + any_ok = cpm_ok; + } + } + + const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{ + .condition = any_ok.?, + .then_body = undefined, // populated below + .else_body = undefined, // populated below + }, .{}); + const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{ + .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), + }); + + // reset cond_scope for then_body + case_scope.instructions.items.len = 0; + try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target, target_ptr); + condbr.positionals.then_body = .{ + .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), + }; + + // reset cond_scope for else_body + case_scope.instructions.items.len = 0; + _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{ + .block = cond_block, + }, .{}); + condbr.positionals.else_body = .{ + .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), + }; + } + + // Finally generate else block or a break. + if (special_case) |case| { + try switchCaseExpr(mod, &else_scope.base, block_scope.break_result_loc, block, case, target, target_ptr); + } else { + // Not handling all possible cases is a compile error. + _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe); + } + switch_inst.castTag(.switchbr).?.positionals.else_body = .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), + }; + + return &block.base; +} + +fn switchCaseExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + block: *zir.Inst.Block, + case: *ast.Node.SwitchCase, + target: *zir.Inst, + target_ptr: ?*zir.Inst, +) !void { + const tree = scope.tree(); + const case_src = tree.token_locs[case.firstToken()].start; + const sub_scope = blk: { + const uncasted_payload = case.payload orelse break :blk scope; + const payload = uncasted_payload.castTag(.PointerPayload).?; + const is_ptr = payload.ptr_token != null; + const value_name = tree.tokenSlice(payload.value_symbol.firstToken()); + if (mem.eql(u8, value_name, "_")) { + if (is_ptr) { + return mod.failTok(scope, payload.ptr_token.?, "pointer modifier invalid on discard", .{}); + } + break :blk scope; + } + return mod.failNode(scope, payload.value_symbol, "TODO implement switch value payload", .{}); + }; + + const case_body = try expr(mod, sub_scope, rl, case.expr); + if (!case_body.tag.isNoReturn()) { + _ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{ + .block = block, + .operand = case_body, + }, .{}); + } +} + fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[cfe.ltoken].start; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 39fa80ea3d..cb3271a57f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -414,11 +414,13 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .loop => try genLoop(o, inst.castTag(.loop).?), .condbr => try genCondBr(o, inst.castTag(.condbr).?), .br => try genBr(o, inst.castTag(.br).?), - .brvoid => try genBrVoid(o, inst.castTag(.brvoid).?.block), + .br_void => try genBrVoid(o, inst.castTag(.br_void).?.block), .switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?), - // booland and boolor are non-short-circuit operations - .booland, .bitand => try genBinOp(o, inst.castTag(.booland).?, " & "), - .boolor, .bitor => try genBinOp(o, inst.castTag(.boolor).?, " | "), + // bool_and and bool_or are non-short-circuit operations + .bool_and => try genBinOp(o, inst.castTag(.bool_and).?, " & "), + .bool_or => try genBinOp(o, inst.castTag(.bool_or).?, " | "), + .bit_and => try genBinOp(o, inst.castTag(.bit_and).?, " & "), + .bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "), .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "), .not => try genUnOp(o, inst.castTag(.not).?, "!"), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), diff --git a/src/zir.zig b/src/zir.zig index 2559fcdc8e..30bfeead9b 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -338,6 +338,12 @@ pub const Inst = struct { enum_type, /// Does nothing; returns a void value. void_value, + /// A switch expression. + switchbr, + /// A range in a switch case, `lhs...rhs`. + /// Only checks that `lhs >= rhs` if they are ints, everything else is + /// validated by the .switch instruction. + switch_range, pub fn Type(tag: Tag) type { return switch (tag) { @@ -435,6 +441,7 @@ pub const Inst = struct { .error_union_type, .merge_error_sets, .slice_start, + .switch_range, => BinOp, .block, @@ -478,6 +485,7 @@ pub const Inst = struct { .enum_type => EnumType, .union_type => UnionType, .struct_type => StructType, + .switchbr => SwitchBr, }; } @@ -605,6 +613,8 @@ pub const Inst = struct { .union_type, .struct_type, .void_value, + .switch_range, + .switchbr, => false, .@"break", @@ -1171,6 +1181,36 @@ pub const Inst = struct { none, }; }; + + pub const SwitchBr = struct { + pub const base_tag = Tag.switchbr; + base: Inst, + + positionals: struct { + target: *Inst, + /// List of all individual items and ranges + items: []*Inst, + cases: []Case, + else_body: Body, + }, + kw_args: struct { + /// Pointer to first range if such exists. + range: ?*Inst = null, + special_prong: SpecialProng = .none, + }, + + // Not anonymous due to stage1 limitations + pub const SpecialProng = enum { + none, + @"else", + underscore, + }; + + pub const Case = struct { + item: *Inst, + body: Body, + }; + }; }; pub const ErrorMsg = struct { @@ -1431,6 +1471,26 @@ const Writer = struct { } try stream.writeByte(']'); }, + []Inst.SwitchBr.Case => { + if (param.len == 0) { + return stream.writeAll("{}"); + } + try stream.writeAll("{\n"); + for (param) |*case, i| { + if (i != 0) { + try stream.writeAll(",\n"); + } + try stream.writeByteNTimes(' ', self.indent); + self.indent += 2; + try self.writeParamToStream(stream, &case.item); + try stream.writeAll(" => "); + try self.writeParamToStream(stream, &case.body); + self.indent -= 2; + } + try stream.writeByte('\n'); + try stream.writeByteNTimes(' ', self.indent - 2); + try stream.writeByte('}'); + }, else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 301b95ad97..f373d7174d 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -154,6 +154,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?), .bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?), .void_value => return mod.constVoid(scope, old_inst.src), + .switchbr => return zirSwitchBr(mod, scope, old_inst.castTag(.switchbr).?), + .switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?), .container_field_named, .container_field_typed, @@ -1535,6 +1537,232 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError! return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); } +fn zirSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + const start = try resolveInst(mod, scope, inst.positionals.lhs); + const end = try resolveInst(mod, scope, inst.positionals.rhs); + + switch (start.ty.zigTypeTag()) { + .Int, .ComptimeInt => {}, + else => return mod.constVoid(scope, inst.base.src), + } + switch (end.ty.zigTypeTag()) { + .Int, .ComptimeInt => {}, + else => return mod.constVoid(scope, inst.base.src), + } + // .switch_range must be inside a comptime scope + const start_val = start.value().?; + const end_val = end.value().?; + if (start_val.compare(.gte, end_val)) { + return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{}); + } + return mod.constVoid(scope, inst.base.src); +} + +fn zirSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + const target = try resolveInst(mod, scope, inst.positionals.target); + try validateSwitch(mod, scope, target, inst); + + if (try mod.resolveDefinedValue(scope, target)) |target_val| { + for (inst.positionals.cases) |case| { + const resolved = try resolveInst(mod, scope, case.item); + const casted = try mod.coerce(scope, target.ty, resolved); + const item = try mod.resolveConstValue(scope, casted); + + if (target_val.eql(item)) { + try analyzeBody(mod, scope.cast(Scope.Block).?, case.body); + return mod.constNoReturn(scope, inst.base.src); + } + } + try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body); + return mod.constNoReturn(scope, inst.base.src); + } + + if (inst.positionals.cases.len == 0) { + // no cases just analyze else_branch + try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body); + return mod.constNoReturn(scope, inst.base.src); + } + + const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); + const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len); + + var case_block: Scope.Block = .{ + .parent = parent_block, + .inst_table = parent_block.inst_table, + .func = parent_block.func, + .owner_decl = parent_block.owner_decl, + .src_decl = parent_block.src_decl, + .instructions = .{}, + .arena = parent_block.arena, + .inlining = parent_block.inlining, + .is_comptime = parent_block.is_comptime, + .branch_quota = parent_block.branch_quota, + }; + defer case_block.instructions.deinit(mod.gpa); + + for (inst.positionals.cases) |case, i| { + // Reset without freeing. + case_block.instructions.items.len = 0; + + const resolved = try resolveInst(mod, scope, case.item); + const casted = try mod.coerce(scope, target.ty, resolved); + const item = try mod.resolveConstValue(scope, casted); + + try analyzeBody(mod, &case_block, case.body); + + cases[i] = .{ + .item = item, + .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) }, + }; + } + + case_block.instructions.items.len = 0; + try analyzeBody(mod, &case_block, inst.positionals.else_body); + + const else_body: ir.Body = .{ + .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items), + }; + + return mod.addSwitchBr(parent_block, inst.base.src, target, cases, else_body); +} + +fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void { + // validate usage of '_' prongs + if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) { + return mod.fail(scope, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{}); + // TODO notes "'_' prong here" inst.positionals.cases[last].src + } + + // check that target type supports ranges + if (inst.kw_args.range) |range_inst| { + switch (target.ty.zigTypeTag()) { + .Int, .ComptimeInt => {}, + else => { + return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty}); + // TODO notes "range used here" range_inst.src + }, + } + } + + // validate for duplicate items/missing else prong + switch (target.ty.zigTypeTag()) { + .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}), + .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}), + .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}), + .Int, .ComptimeInt => { + var range_set = @import("RangeSet.zig").init(mod.gpa); + defer range_set.deinit(); + + for (inst.positionals.items) |item| { + const maybe_src = if (item.castTag(.switch_range)) |range| blk: { + const start_resolved = try resolveInst(mod, scope, range.positionals.lhs); + const start_casted = try mod.coerce(scope, target.ty, start_resolved); + const end_resolved = try resolveInst(mod, scope, range.positionals.rhs); + const end_casted = try mod.coerce(scope, target.ty, end_resolved); + + break :blk try range_set.add( + try mod.resolveConstValue(scope, start_casted), + try mod.resolveConstValue(scope, end_casted), + item.src, + ); + } else blk: { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, target.ty, resolved); + const value = try mod.resolveConstValue(scope, casted); + break :blk try range_set.add(value, value, item.src); + }; + + if (maybe_src) |previous_src| { + return mod.fail(scope, item.src, "duplicate switch value", .{}); + // TODO notes "previous value is here" previous_src + } + } + + if (target.ty.zigTypeTag() == .Int) { + var arena = std.heap.ArenaAllocator.init(mod.gpa); + defer arena.deinit(); + + const start = try target.ty.minInt(&arena, mod.getTarget()); + const end = try target.ty.maxInt(&arena, mod.getTarget()); + if (try range_set.spans(start, end)) { + if (inst.kw_args.special_prong == .@"else") { + return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); + } + return; + } + } + + if (inst.kw_args.special_prong != .@"else") { + return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); + } + }, + .Bool => { + var true_count: u8 = 0; + var false_count: u8 = 0; + for (inst.positionals.items) |item| { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, Type.initTag(.bool), resolved); + if ((try mod.resolveConstValue(scope, casted)).toBool()) { + true_count += 1; + } else { + false_count += 1; + } + + if (true_count + false_count > 2) { + return mod.fail(scope, item.src, "duplicate switch value", .{}); + } + } + if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") { + return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); + } + if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") { + return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); + } + }, + .EnumLiteral, .Void, .Fn, .Pointer, .Type => { + if (inst.kw_args.special_prong != .@"else") { + return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty}); + } + + var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa); + defer seen_values.deinit(); + + for (inst.positionals.items) |item| { + const resolved = try resolveInst(mod, scope, item); + const casted = try mod.coerce(scope, target.ty, resolved); + const val = try mod.resolveConstValue(scope, casted); + + if (try seen_values.fetchPut(val, item.src)) |prev| { + return mod.fail(scope, item.src, "duplicate switch value", .{}); + // TODO notes "previous value here" prev.value + } + } + }, + + .ErrorUnion, + .NoReturn, + .Array, + .Struct, + .Undefined, + .Null, + .Optional, + .BoundFn, + .Opaque, + .Vector, + .Frame, + .AnyFrame, + .ComptimeFloat, + .Float, + => { + return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty}); + }, + } +} + fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); -- cgit v1.2.3 From aa3e0ff454d06407b4ee347c1cd0c1e09444c52c Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 3 Feb 2021 22:15:18 +0100 Subject: Create type declarations for extern functions and write the 'import' section --- lib/std/wasm.zig | 14 +++++++++++ src/codegen/wasm.zig | 28 +++++++++++++++------- src/link/Wasm.zig | 66 ++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 93 insertions(+), 15 deletions(-) (limited to 'src/codegen') diff --git a/lib/std/wasm.zig b/lib/std/wasm.zig index aa087c89c9..c59117d65b 100644 --- a/lib/std/wasm.zig +++ b/lib/std/wasm.zig @@ -253,6 +253,20 @@ pub fn section(val: Section) u8 { return @enumToInt(val); } +/// The kind of the type when importing or exporting to/from the host environment +/// https://webassembly.github.io/spec/core/syntax/modules.html +pub const ExternalKind = enum(u8) { + function, + table, + memory, + global, +}; + +/// Returns the integer value of a given `ExternalKind` +pub fn kind(val: ExternalKind) u8 { + return @enumToInt(val); +} + // types pub const element_type: u8 = 0x70; pub const function_type: u8 = 0x60; diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index abc411b551..5be26de7cb 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -163,13 +163,18 @@ pub const Context = struct { try self.genFunctype(); const writer = self.code.writer(); - // Reserve space to write the size after generating the code as well as space for locals count - try self.code.resize(10); - // Write instructions // TODO: check for and handle death of instructions const tv = self.decl.typed_value.most_recent.typed_value; - const mod_fn = tv.val.castTag(.function).?.data; + const mod_fn = blk: { + if (tv.val.castTag(.function)) |func| break :blk func.data; + if (tv.val.castTag(.extern_fn)) |ext_fn| return; // don't need codegen for extern functions + return self.fail(self.decl.src(), "TODO: Wasm codegen for decl type '{s}'", .{tv.ty.tag()}); + }; + + // Reserve space to write the size after generating the code as well as space for locals count + try self.code.resize(10); + try self.genBody(mod_fn.body); // finally, write our local types at the 'offset' position @@ -239,9 +244,16 @@ pub const Context = struct { fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue { const func_inst = inst.func.castTag(.constant).?; - const func = func_inst.val.castTag(.function).?.data; - const target = func.owner_decl; - const target_ty = target.typed_value.most_recent.typed_value.ty; + const func_val = inst.func.value().?; + + const target = blk: { + if (func_val.castTag(.function)) |func| { + break :blk func.data.owner_decl; + } else if (func_val.castTag(.extern_fn)) |ext_fn| { + break :blk ext_fn.data; + } + return self.fail(inst.base.src, "Expected a function, but instead found type '{s}'", .{func_val.tag()}); + }; for (inst.args) |arg| { const arg_val = self.resolveInst(arg); @@ -495,7 +507,7 @@ pub const Context = struct { } fn genBr(self: *Context, br: *Inst.Br) InnerError!WValue { - // of operand has codegen bits we should break with a value + // if operand has codegen bits we should break with a value if (br.operand.ty.hasCodeGenBits()) { const operand = self.resolveInst(br.operand); try self.emitWValue(operand); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 547ab2a012..9660ff0a43 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -85,8 +85,6 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { const typed_value = decl.typed_value.most_recent.typed_value; if (typed_value.ty.zigTypeTag() != .Fn) return error.TODOImplementNonFnDeclsForWasm; - if (typed_value.val.tag() == .extern_fn) - return error.TODOImplementExternFnDeclsForWasm; if (decl.fn_link.wasm) |*fn_data| { fn_data.functype.items.len = 0; @@ -184,17 +182,63 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { ); } + // Import section + { + // TODO: implement non-functions imports + const header_offset = try reserveVecSectionHeader(file); + const writer = file.writer(); + var count: u32 = 0; + for (self.funcs.items) |decl, typeidx| { + if (decl.typed_value.most_recent.typed_value.val.tag() != .extern_fn) { + continue; + } + + // TODO: can we set/save the module name somewhere? + // For now, emit "env" like LLVM does + const module_name = "env"; + try leb.writeULEB128(writer, @intCast(u32, module_name.len)); + try writer.writeAll(module_name); + + // wasm requires the length of the import name and doesn't require a null-termination + const decl_len = mem.len(decl.name); + try leb.writeULEB128(writer, @intCast(u32, decl_len)); + try writer.writeAll(decl.name[0..decl_len]); + + // emit kind and the function type + try writer.writeByte(wasm.kind(.function)); + try leb.writeULEB128(writer, @intCast(u32, typeidx)); + + count += 1; + } + + try writeVecSectionHeader( + file, + header_offset, + .import, + @intCast(u32, (try file.getPos()) - header_offset - header_size), + count, + ); + } + // Function section { const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); - for (self.funcs.items) |_, typeidx| try leb.writeULEB128(writer, @intCast(u32, typeidx)); + var count: u32 = 0; + for (self.funcs.items) |decl, typeidx| { + // Extern functions only have a type, so skip the function signature section + if (decl.typed_value.most_recent.typed_value.val.tag() != .function) { + continue; + } + try leb.writeULEB128(writer, @intCast(u32, typeidx)); + count += 1; + } try writeVecSectionHeader( file, header_offset, .function, @intCast(u32, (try file.getPos()) - header_offset - header_size), - @intCast(u32, self.funcs.items.len), + count, ); } @@ -214,7 +258,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // Type of the export try writer.writeByte(0x00); // Exported function index - try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?); + try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).? + 1); }, else => return error.TODOImplementNonFnDeclsForWasm, } @@ -235,7 +279,13 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { { const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); + var count: u32 = 0; for (self.funcs.items) |decl| { + // Do not emit any code for extern functions + if (decl.typed_value.most_recent.typed_value.val.tag() != .function) { + std.debug.print("Skipping decl: {s}\n", .{decl.name}); + continue; + } const fn_data = &decl.fn_link.wasm.?; // Write the already generated code to the file, inserting @@ -247,18 +297,20 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // Use a fixed width here to make calculating the code size // in codegen.wasm.gen() simpler. var buf: [5]u8 = undefined; - leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?); + std.debug.print("idx_ref: {s} - {d}\n", .{ idx_ref.decl.name, self.getFuncidx(idx_ref.decl).? }); + leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).? - 1); try writer.writeAll(&buf); } try writer.writeAll(fn_data.code.items[current..]); + count += 1; } try writeVecSectionHeader( file, header_offset, .code, @intCast(u32, (try file.getPos()) - header_offset - header_size), - @intCast(u32, self.funcs.items.len), + count, ); } } -- cgit v1.2.3 From 36df6a008fb009fd8fd1d3362fc850cf97723dcd Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Thu, 4 Feb 2021 21:08:19 +0100 Subject: Ensure function indices are correct and fix a memory leak --- lib/std/wasm.zig | 2 +- src/codegen/wasm.zig | 2 +- src/link/Wasm.zig | 118 +++++++++++++++++++++++++++++++-------------------- 3 files changed, 73 insertions(+), 49 deletions(-) (limited to 'src/codegen') diff --git a/lib/std/wasm.zig b/lib/std/wasm.zig index c59117d65b..a04378d283 100644 --- a/lib/std/wasm.zig +++ b/lib/std/wasm.zig @@ -263,7 +263,7 @@ pub const ExternalKind = enum(u8) { }; /// Returns the integer value of a given `ExternalKind` -pub fn kind(val: ExternalKind) u8 { +pub fn externalKind(val: ExternalKind) u8 { return @enumToInt(val); } diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 5be26de7cb..34e0b2f9b5 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -161,7 +161,6 @@ pub const Context = struct { pub fn gen(self: *Context) InnerError!void { assert(self.code.items.len == 0); try self.genFunctype(); - const writer = self.code.writer(); // Write instructions // TODO: check for and handle death of instructions @@ -194,6 +193,7 @@ pub const Context = struct { } } + const writer = self.code.writer(); try writer.writeByte(wasm.opcode(.end)); // Fill in the size of the generated code to the reserved space at the diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 9660ff0a43..bd89ce3345 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -33,9 +33,18 @@ base: link.File, /// List of all function Decls to be written to the output file. The index of /// each Decl in this list at the time of writing the binary is used as the -/// function index. +/// function index. In the event where ext_funcs' size is not 0, the index of +/// each function is added on top of the ext_funcs' length. /// TODO: can/should we access some data structure in Module directly? funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, +/// List of all extern function Decls to be written to the `import` section of the +/// wasm binary. The positin in the list defines the function index +ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, +/// When importing objects from the host environment, a name must be supplied. +/// LLVM uses "env" by default when none is given. This would be a good default for Zig +/// to support existing code. +/// TODO: Allow setting this through a flag? +host_name: []const u8 = "env", pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm { assert(options.object_format == .wasm); @@ -76,7 +85,13 @@ pub fn deinit(self: *Wasm) void { decl.fn_link.wasm.?.code.deinit(self.base.allocator); decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); } + for (self.ext_funcs.items) |decl| { + decl.fn_link.wasm.?.functype.deinit(self.base.allocator); + decl.fn_link.wasm.?.code.deinit(self.base.allocator); + decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); + } self.funcs.deinit(self.base.allocator); + self.ext_funcs.deinit(self.base.allocator); } // Generate code for the Decl, storing it in memory to be later written to @@ -92,7 +107,12 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { fn_data.idx_refs.items.len = 0; } else { decl.fn_link.wasm = .{}; - try self.funcs.append(self.base.allocator, decl); + // dependent on function type, appends it to the correct list + switch (decl.typed_value.most_recent.typed_value.val.tag()) { + .function => try self.funcs.append(self.base.allocator, decl), + .extern_fn => try self.ext_funcs.append(self.base.allocator, decl), + else => return error.TODOImplementNonFnDeclsForWasm, + } } const fn_data = &decl.fn_link.wasm.?; @@ -141,7 +161,12 @@ pub fn updateDeclExports( pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { // TODO: remove this assert when non-function Decls are implemented assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn); - _ = self.funcs.swapRemove(self.getFuncidx(decl).?); + const func_idx = self.getFuncidx(decl).?; + switch (decl.typed_value.most_recent.typed_value.val.tag()) { + .function => _ = self.funcs.swapRemove(func_idx), + .extern_fn => _ = self.ext_funcs.swapRemove(func_idx), + else => unreachable, + } decl.fn_link.wasm.?.functype.deinit(self.base.allocator); decl.fn_link.wasm.?.code.deinit(self.base.allocator); decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator); @@ -170,15 +195,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // Type section { const header_offset = try reserveVecSectionHeader(file); - for (self.funcs.items) |decl| { - try file.writeAll(decl.fn_link.wasm.?.functype.items); - } + + // extern functions are defined in the wasm binary first through the `import` + // section, so define their func types first + for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items); + for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.?.functype.items); + try writeVecSectionHeader( file, header_offset, .type, @intCast(u32, (try file.getPos()) - header_offset - header_size), - @intCast(u32, self.funcs.items.len), + @intCast(u32, self.ext_funcs.items.len + self.funcs.items.len), ); } @@ -187,28 +215,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // TODO: implement non-functions imports const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); - var count: u32 = 0; - for (self.funcs.items) |decl, typeidx| { - if (decl.typed_value.most_recent.typed_value.val.tag() != .extern_fn) { - continue; - } - - // TODO: can we set/save the module name somewhere? - // For now, emit "env" like LLVM does - const module_name = "env"; - try leb.writeULEB128(writer, @intCast(u32, module_name.len)); - try writer.writeAll(module_name); + for (self.ext_funcs.items) |decl, typeidx| { + try leb.writeULEB128(writer, @intCast(u32, self.host_name.len)); + try writer.writeAll(self.host_name); - // wasm requires the length of the import name and doesn't require a null-termination + // wasm requires the length of the import name with no null-termination const decl_len = mem.len(decl.name); try leb.writeULEB128(writer, @intCast(u32, decl_len)); try writer.writeAll(decl.name[0..decl_len]); // emit kind and the function type - try writer.writeByte(wasm.kind(.function)); + try writer.writeByte(wasm.externalKind(.function)); try leb.writeULEB128(writer, @intCast(u32, typeidx)); - - count += 1; } try writeVecSectionHeader( @@ -216,7 +234,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { header_offset, .import, @intCast(u32, (try file.getPos()) - header_offset - header_size), - count, + @intCast(u32, self.ext_funcs.items.len), ); } @@ -224,21 +242,17 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { { const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); - var count: u32 = 0; - for (self.funcs.items) |decl, typeidx| { - // Extern functions only have a type, so skip the function signature section - if (decl.typed_value.most_recent.typed_value.val.tag() != .function) { - continue; - } - try leb.writeULEB128(writer, @intCast(u32, typeidx)); - count += 1; + for (self.funcs.items) |_, typeidx| { + const func_idx = @intCast(u32, self.getFuncIdxOffset() + typeidx); + try leb.writeULEB128(writer, func_idx); } + try writeVecSectionHeader( file, header_offset, .function, @intCast(u32, (try file.getPos()) - header_offset - header_size), - count, + @intCast(u32, self.funcs.items.len), ); } @@ -256,9 +270,9 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { switch (exprt.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { .Fn => { // Type of the export - try writer.writeByte(0x00); + try writer.writeByte(wasm.externalKind(.function)); // Exported function index - try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).? + 1); + try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?); }, else => return error.TODOImplementNonFnDeclsForWasm, } @@ -279,13 +293,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { { const header_offset = try reserveVecSectionHeader(file); const writer = file.writer(); - var count: u32 = 0; for (self.funcs.items) |decl| { - // Do not emit any code for extern functions - if (decl.typed_value.most_recent.typed_value.val.tag() != .function) { - std.debug.print("Skipping decl: {s}\n", .{decl.name}); - continue; - } const fn_data = &decl.fn_link.wasm.?; // Write the already generated code to the file, inserting @@ -297,20 +305,18 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void { // Use a fixed width here to make calculating the code size // in codegen.wasm.gen() simpler. var buf: [5]u8 = undefined; - std.debug.print("idx_ref: {s} - {d}\n", .{ idx_ref.decl.name, self.getFuncidx(idx_ref.decl).? }); - leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).? - 1); + leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?); try writer.writeAll(&buf); } try writer.writeAll(fn_data.code.items[current..]); - count += 1; } try writeVecSectionHeader( file, header_offset, .code, @intCast(u32, (try file.getPos()) - header_offset - header_size), - count, + @intCast(u32, self.funcs.items.len), ); } } @@ -575,13 +581,31 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { } /// Get the current index of a given Decl in the function list -/// TODO: we could maintain a hash map to potentially make this +/// This will correctly provide the index, regardless whether the function is extern or not +/// TODO: we could maintain a hash map to potentially make this simpler fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 { - return for (self.funcs.items) |func, idx| { - if (func == decl) break @intCast(u32, idx); + var offset: u32 = 0; + const slice = switch (decl.typed_value.most_recent.typed_value.val.tag()) { + .function => blk: { + // when the target is a regular function, we have to calculate + // the offset of where the index starts + offset += self.getFuncIdxOffset(); + break :blk self.funcs.items; + }, + .extern_fn => self.ext_funcs.items, + else => return null, + }; + return for (slice) |func, idx| { + if (func == decl) break @intCast(u32, offset + idx); } else null; } +/// Based on the size of `ext_funcs` returns the +/// offset of the function indices +fn getFuncIdxOffset(self: Wasm) u32 { + return @intCast(u32, self.ext_funcs.items.len); +} + fn reserveVecSectionHeader(file: fs.File) !u64 { // section id + fixed leb contents size + fixed leb vector length const header_size = 1 + 5 + 5; -- cgit v1.2.3 From c2beaba85a87b5985fe9f1676ad2bc4888dd6c1a Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Mon, 8 Feb 2021 22:29:41 +0100 Subject: stage2 ARM: fix callee_preserved_regs Previously, the registers included r0, r1, r2, r3 which are not included in the callee saved registers according to the Procedure Call Standard for the ARM Architecture. --- src/codegen/arm.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/codegen') diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig index 94f1ae951d..d538d28c50 100644 --- a/src/codegen/arm.zig +++ b/src/codegen/arm.zig @@ -186,7 +186,7 @@ pub const Psr = enum { spsr, }; -pub const callee_preserved_regs = [_]Register{ .r0, .r1, .r2, .r3, .r4, .r5, .r6, .r7, .r8, .r10 }; +pub const callee_preserved_regs = [_]Register{ .r4, .r5, .r6, .r7, .r8, .r10 }; pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 }; pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 }; -- cgit v1.2.3 From 070e548acf8b5cb22459b779ce771b42157f49f7 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Tue, 16 Feb 2021 22:06:35 +0100 Subject: std: remove io.AutoIndentingStream This type is not widely applicable enough to be a public part of the public interface of the std. The current implementation in only fully utilized by the zig fmt implementation, which could benefit by even tighter integration as will be demonstrated in the next commit. Therefore, move the current io.AutoIndentingStream to lib/std/zig/render.zig. The C backend of the self hosted compiler also use this type currently, but it does not require anywhere near its full complexity. Therefore, implement a greatly simplified version of this interface in src/codegen/c.zig. --- CMakeLists.txt | 1 - lib/std/io.zig | 3 - lib/std/io/auto_indenting_stream.zig | 154 ----------------------------------- lib/std/zig/render.zig | 142 +++++++++++++++++++++++++++++++- src/codegen/c.zig | 58 ++++++++++++- src/link/C.zig | 2 +- 6 files changed, 196 insertions(+), 164 deletions(-) delete mode 100644 lib/std/io/auto_indenting_stream.zig (limited to 'src/codegen') diff --git a/CMakeLists.txt b/CMakeLists.txt index a0c3ae84fa..c203a493c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -370,7 +370,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/heap.zig" "${CMAKE_SOURCE_DIR}/lib/std/heap/arena_allocator.zig" "${CMAKE_SOURCE_DIR}/lib/std/io.zig" - "${CMAKE_SOURCE_DIR}/lib/std/io/auto_indenting_stream.zig" "${CMAKE_SOURCE_DIR}/lib/std/io/buffered_atomic_file.zig" "${CMAKE_SOURCE_DIR}/lib/std/io/buffered_writer.zig" "${CMAKE_SOURCE_DIR}/lib/std/io/change_detection_stream.zig" diff --git a/lib/std/io.zig b/lib/std/io.zig index 240faaa452..b529c57866 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -142,9 +142,6 @@ pub const bitReader = @import("io/bit_reader.zig").bitReader; pub const BitWriter = @import("io/bit_writer.zig").BitWriter; pub const bitWriter = @import("io/bit_writer.zig").bitWriter; -pub const AutoIndentingStream = @import("io/auto_indenting_stream.zig").AutoIndentingStream; -pub const autoIndentingStream = @import("io/auto_indenting_stream.zig").autoIndentingStream; - pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream; pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream; diff --git a/lib/std/io/auto_indenting_stream.zig b/lib/std/io/auto_indenting_stream.zig deleted file mode 100644 index 8f8b981b9b..0000000000 --- a/lib/std/io/auto_indenting_stream.zig +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2021 Zig Contributors -// This file is part of [zig](https://ziglang.org/), which is MIT licensed. -// The MIT license requires this copyright notice to be included in all copies -// and substantial portions of the software. - -const std = @import("../std.zig"); -const io = std.io; -const mem = std.mem; -const assert = std.debug.assert; - -/// Automatically inserts indentation of written data by keeping -/// track of the current indentation level -pub fn AutoIndentingStream(comptime UnderlyingWriter: type) type { - return struct { - const Self = @This(); - pub const Error = UnderlyingWriter.Error; - pub const Writer = io.Writer(*Self, Error, write); - - underlying_writer: UnderlyingWriter, - - indent_count: usize = 0, - indent_delta: usize, - current_line_empty: bool = true, - indent_one_shot_count: usize = 0, // automatically popped when applied - applied_indent: usize = 0, // the most recently applied indent - indent_next_line: usize = 0, // not used until the next line - - pub fn writer(self: *Self) Writer { - return .{ .context = self }; - } - - pub fn write(self: *Self, bytes: []const u8) Error!usize { - if (bytes.len == 0) - return @as(usize, 0); - - try self.applyIndent(); - return self.writeNoIndent(bytes); - } - - // Change the indent delta without changing the final indentation level - pub fn setIndentDelta(self: *Self, indent_delta: usize) void { - if (self.indent_delta == indent_delta) { - return; - } else if (self.indent_delta > indent_delta) { - assert(self.indent_delta % indent_delta == 0); - self.indent_count = self.indent_count * (self.indent_delta / indent_delta); - } else { - // assert that the current indentation (in spaces) in a multiple of the new delta - assert((self.indent_count * self.indent_delta) % indent_delta == 0); - self.indent_count = self.indent_count / (indent_delta / self.indent_delta); - } - self.indent_delta = indent_delta; - } - - fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize { - if (bytes.len == 0) - return @as(usize, 0); - - try self.underlying_writer.writeAll(bytes); - if (bytes[bytes.len - 1] == '\n') - self.resetLine(); - return bytes.len; - } - - pub fn insertNewline(self: *Self) Error!void { - _ = try self.writeNoIndent("\n"); - } - - fn resetLine(self: *Self) void { - self.current_line_empty = true; - self.indent_next_line = 0; - } - - /// Insert a newline unless the current line is blank - pub fn maybeInsertNewline(self: *Self) Error!void { - if (!self.current_line_empty) - try self.insertNewline(); - } - - /// Push default indentation - pub fn pushIndent(self: *Self) void { - // Doesn't actually write any indentation. - // Just primes the stream to be able to write the correct indentation if it needs to. - self.indent_count += 1; - } - - /// Push an indent that is automatically popped after being applied - pub fn pushIndentOneShot(self: *Self) void { - self.indent_one_shot_count += 1; - self.pushIndent(); - } - - /// Turns all one-shot indents into regular indents - /// Returns number of indents that must now be manually popped - pub fn lockOneShotIndent(self: *Self) usize { - var locked_count = self.indent_one_shot_count; - self.indent_one_shot_count = 0; - return locked_count; - } - - /// Push an indent that should not take effect until the next line - pub fn pushIndentNextLine(self: *Self) void { - self.indent_next_line += 1; - self.pushIndent(); - } - - pub fn popIndent(self: *Self) void { - assert(self.indent_count != 0); - self.indent_count -= 1; - - if (self.indent_next_line > 0) - self.indent_next_line -= 1; - } - - /// Writes ' ' bytes if the current line is empty - fn applyIndent(self: *Self) Error!void { - const current_indent = self.currentIndent(); - if (self.current_line_empty and current_indent > 0) { - try self.underlying_writer.writeByteNTimes(' ', current_indent); - self.applied_indent = current_indent; - } - - self.indent_count -= self.indent_one_shot_count; - self.indent_one_shot_count = 0; - self.current_line_empty = false; - } - - /// Checks to see if the most recent indentation exceeds the currently pushed indents - pub fn isLineOverIndented(self: *Self) bool { - if (self.current_line_empty) return false; - return self.applied_indent > self.currentIndent(); - } - - fn currentIndent(self: *Self) usize { - var indent_current: usize = 0; - if (self.indent_count > 0) { - const indent_count = self.indent_count - self.indent_next_line; - indent_current = indent_count * self.indent_delta; - } - return indent_current; - } - }; -} - -pub fn autoIndentingStream( - indent_delta: usize, - underlying_writer: anytype, -) AutoIndentingStream(@TypeOf(underlying_writer)) { - return AutoIndentingStream(@TypeOf(underlying_writer)){ - .underlying_writer = underlying_writer, - .indent_delta = indent_delta, - }; -} diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 2d9c2ae9a9..e668c4d64d 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -15,12 +15,14 @@ const asm_indent_delta = 2; pub const Error = ast.Tree.RenderError; -const Writer = std.ArrayList(u8).Writer; -const Ais = std.io.AutoIndentingStream(Writer); +const Ais = AutoIndentingStream(std.ArrayList(u8).Writer); pub fn renderTree(buffer: *std.ArrayList(u8), tree: ast.Tree) Error!void { assert(tree.errors.len == 0); // Cannot render an invalid tree. - var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, buffer.writer()); + var auto_indenting_stream = Ais{ + .indent_delta = indent_delta, + .underlying_writer = buffer.writer(), + }; const ais = &auto_indenting_stream; // Render all the line comments at the beginning of the file. @@ -2132,3 +2134,137 @@ fn nodeCausesSliceOpSpace(tag: ast.Node.Tag) bool { else => false, }; } + +/// Automatically inserts indentation of written data by keeping +/// track of the current indentation level +fn AutoIndentingStream(comptime UnderlyingWriter: type) type { + return struct { + const Self = @This(); + pub const Error = UnderlyingWriter.Error; + pub const Writer = std.io.Writer(*Self, Error, write); + + underlying_writer: UnderlyingWriter, + + indent_count: usize = 0, + indent_delta: usize, + current_line_empty: bool = true, + indent_one_shot_count: usize = 0, // automatically popped when applied + applied_indent: usize = 0, // the most recently applied indent + indent_next_line: usize = 0, // not used until the next line + + pub fn writer(self: *Self) Writer { + return .{ .context = self }; + } + + pub fn write(self: *Self, bytes: []const u8) Error!usize { + if (bytes.len == 0) + return @as(usize, 0); + + try self.applyIndent(); + return self.writeNoIndent(bytes); + } + + // Change the indent delta without changing the final indentation level + pub fn setIndentDelta(self: *Self, new_indent_delta: usize) void { + if (self.indent_delta == new_indent_delta) { + return; + } else if (self.indent_delta > new_indent_delta) { + assert(self.indent_delta % new_indent_delta == 0); + self.indent_count = self.indent_count * (self.indent_delta / new_indent_delta); + } else { + // assert that the current indentation (in spaces) in a multiple of the new delta + assert((self.indent_count * self.indent_delta) % new_indent_delta == 0); + self.indent_count = self.indent_count / (new_indent_delta / self.indent_delta); + } + self.indent_delta = new_indent_delta; + } + + fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize { + if (bytes.len == 0) + return @as(usize, 0); + + try self.underlying_writer.writeAll(bytes); + if (bytes[bytes.len - 1] == '\n') + self.resetLine(); + return bytes.len; + } + + pub fn insertNewline(self: *Self) Error!void { + _ = try self.writeNoIndent("\n"); + } + + fn resetLine(self: *Self) void { + self.current_line_empty = true; + self.indent_next_line = 0; + } + + /// Insert a newline unless the current line is blank + pub fn maybeInsertNewline(self: *Self) Error!void { + if (!self.current_line_empty) + try self.insertNewline(); + } + + /// Push default indentation + pub fn pushIndent(self: *Self) void { + // Doesn't actually write any indentation. + // Just primes the stream to be able to write the correct indentation if it needs to. + self.indent_count += 1; + } + + /// Push an indent that is automatically popped after being applied + pub fn pushIndentOneShot(self: *Self) void { + self.indent_one_shot_count += 1; + self.pushIndent(); + } + + /// Turns all one-shot indents into regular indents + /// Returns number of indents that must now be manually popped + pub fn lockOneShotIndent(self: *Self) usize { + var locked_count = self.indent_one_shot_count; + self.indent_one_shot_count = 0; + return locked_count; + } + + /// Push an indent that should not take effect until the next line + pub fn pushIndentNextLine(self: *Self) void { + self.indent_next_line += 1; + self.pushIndent(); + } + + pub fn popIndent(self: *Self) void { + assert(self.indent_count != 0); + self.indent_count -= 1; + + if (self.indent_next_line > 0) + self.indent_next_line -= 1; + } + + /// Writes ' ' bytes if the current line is empty + fn applyIndent(self: *Self) Error!void { + const current_indent = self.currentIndent(); + if (self.current_line_empty and current_indent > 0) { + try self.underlying_writer.writeByteNTimes(' ', current_indent); + self.applied_indent = current_indent; + } + + self.indent_count -= self.indent_one_shot_count; + self.indent_one_shot_count = 0; + self.current_line_empty = false; + } + + /// Checks to see if the most recent indentation exceeds the currently pushed indents + pub fn isLineOverIndented(self: *Self) bool { + if (self.current_line_empty) return false; + return self.applied_indent > self.currentIndent(); + } + + fn currentIndent(self: *Self) usize { + var indent_current: usize = 0; + if (self.indent_count > 0) { + const indent_count = self.indent_count - self.indent_next_line; + indent_current = indent_count * self.indent_delta; + } + return indent_current; + } + }; +} diff --git a/src/codegen/c.zig b/src/codegen/c.zig index cb3271a57f..d8c81ad0e4 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const assert = std.debug.assert; const mem = std.mem; const log = std.log.scoped(.c); @@ -42,7 +43,7 @@ pub const Object = struct { next_arg_index: usize = 0, next_local_index: usize = 0, next_block_index: usize = 0, - indent_writer: std.io.AutoIndentingStream(std.ArrayList(u8).Writer), + indent_writer: IndentWriter(std.ArrayList(u8).Writer), fn resolveInst(o: *Object, inst: *Inst) !CValue { if (inst.value()) |_| { @@ -63,7 +64,7 @@ pub const Object = struct { return local_value; } - fn writer(o: *Object) std.io.AutoIndentingStream(std.ArrayList(u8).Writer).Writer { + fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer { return o.indent_writer.writer(); } @@ -796,3 +797,56 @@ 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 IndentWriter(comptime UnderlyingWriter: type) type { + return struct { + const Self = @This(); + pub const Error = UnderlyingWriter.Error; + pub const Writer = std.io.Writer(*Self, Error, write); + + pub const indent_delta = 4; + + underlying_writer: UnderlyingWriter, + indent_count: usize = 0, + current_line_empty: bool = true, + + pub fn writer(self: *Self) Writer { + return .{ .context = self }; + } + + pub fn write(self: *Self, bytes: []const u8) Error!usize { + if (bytes.len == 0) return @as(usize, 0); + + const current_indent = self.indent_count * Self.indent_delta; + if (self.current_line_empty and current_indent > 0) { + try self.underlying_writer.writeByteNTimes(' ', current_indent); + } + self.current_line_empty = false; + + return self.writeNoIndent(bytes); + } + + pub fn insertNewline(self: *Self) Error!void { + _ = try self.writeNoIndent("\n"); + } + + pub fn pushIndent(self: *Self) void { + self.indent_count += 1; + } + + pub fn popIndent(self: *Self) void { + assert(self.indent_count != 0); + self.indent_count -= 1; + } + + fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize { + if (bytes.len == 0) return @as(usize, 0); + + try self.underlying_writer.writeAll(bytes); + if (bytes[bytes.len - 1] == '\n') { + self.current_line_empty = true; + } + return bytes.len; + } + }; +} diff --git a/src/link/C.zig b/src/link/C.zig index 765249cd7d..8fb3637cbe 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -97,7 +97,7 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { .value_map = codegen.CValueMap.init(module.gpa), .indent_writer = undefined, // set later so we can get a pointer to object.code }; - object.indent_writer = std.io.autoIndentingStream(4, object.code.writer()); + object.indent_writer = .{ .underlying_writer = object.code.writer() }; defer object.value_map.deinit(); defer object.code.deinit(); defer object.dg.fwd_decl.deinit(); -- cgit v1.2.3 From 449f4de3825d3448c2aa0cda79c1a567adb08b59 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Feb 2021 21:54:23 -0700 Subject: zig fmt src/ --- src/Module.zig | 9 ++++----- src/astgen.zig | 27 ++++++++++++--------------- src/codegen.zig | 3 +-- src/codegen/c.zig | 3 +-- src/codegen/spirv.zig | 4 +--- src/link/SpirV.zig | 32 ++++++++++++++++---------------- src/main.zig | 3 +-- src/translate_c.zig | 15 ++++++--------- src/translate_c/ast.zig | 6 +++--- 9 files changed, 45 insertions(+), 57 deletions(-) (limited to 'src/codegen') diff --git a/src/Module.zig b/src/Module.zig index 7af4648c79..b0b9d6bc00 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1251,11 +1251,10 @@ fn astgenAndSemaFn( .param_types = param_types, .cc = cc, }); - } else - try astgen.addZirInstTag(mod, &fn_type_scope.base, fn_src, .fn_type, .{ - .return_type = return_type_inst, - .param_types = param_types, - }); + } else try astgen.addZirInstTag(mod, &fn_type_scope.base, fn_src, .fn_type, .{ + .return_type = return_type_inst, + .param_types = param_types, + }); if (std.builtin.mode == .Debug and mod.comp.verbose_ir) { zir.dumpZir(mod.gpa, "fn_type", decl.name, fn_type_scope.instructions.items) catch {}; diff --git a/src/astgen.zig b/src/astgen.zig index 5b27925a5f..1a533a6e96 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -2143,11 +2143,10 @@ fn ifExpr( .src = token_starts[tree.lastToken(else_node)], .result = try expr(mod, sub_scope, block_scope.break_result_loc, else_node), }; - } else - .{ - .src = token_starts[tree.lastToken(if_full.ast.then_expr)], - .result = null, - }; + } else .{ + .src = token_starts[tree.lastToken(if_full.ast.then_expr)], + .result = null, + }; return finishThenElseBlock( mod, @@ -2316,11 +2315,10 @@ fn whileExpr( .src = token_starts[tree.lastToken(else_node)], .result = try expr(mod, sub_scope, loop_scope.break_result_loc, else_node), }; - } else - .{ - .src = token_starts[tree.lastToken(while_full.ast.then_expr)], - .result = null, - }; + } else .{ + .src = token_starts[tree.lastToken(while_full.ast.then_expr)], + .result = null, + }; if (loop_scope.label) |some| { if (!some.used) { @@ -2514,11 +2512,10 @@ fn forExpr( .src = token_starts[tree.lastToken(else_node)], .result = try expr(mod, sub_scope, loop_scope.break_result_loc, else_node), }; - } else - .{ - .src = token_starts[tree.lastToken(for_full.ast.then_expr)], - .result = null, - }; + } else .{ + .src = token_starts[tree.lastToken(for_full.ast.then_expr)], + .result = null, + }; if (loop_scope.label) |some| { if (!some.used) { diff --git a/src/codegen.zig b/src/codegen.zig index 779366cc23..69c7789462 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2950,8 +2950,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { 4, 8 => { const offset = if (math.cast(i9, adj_off)) |imm| Instruction.LoadStoreOffset.imm_post_index(-imm) - else |_| - Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u64), MCValue{ .immediate = adj_off })); + else |_| Instruction.LoadStoreOffset.reg(try self.copyToTmpRegister(src, Type.initTag(.u64), MCValue{ .immediate = adj_off })); const rn: Register = switch (arch) { .aarch64, .aarch64_be => .x29, .aarch64_32 => .w29, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index d8c81ad0e4..a885b984ac 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -626,8 +626,7 @@ fn genBlock(o: *Object, inst: *Inst.Block) !CValue { const local = try o.allocLocal(inst.base.ty, .Mut); try writer.writeAll(";\n"); break :blk local; - } else - CValue{ .none = {} }; + } else CValue{ .none = {} }; inst.codegen.mcv = @bitCast(@import("../codegen.zig").AnyMCValue, result); try genBody(o, inst.body); diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 5a262de836..23fc45616f 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -45,7 +45,5 @@ pub const SPIRVModule = struct { return self.next_id; } - pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void { - - } + pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void {} }; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index bde1eae391..7a35752f62 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -1,17 +1,3 @@ -const SpirV = @This(); - -const std = @import("std"); -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; - -const Module = @import("../Module.zig"); -const Compilation = @import("../Compilation.zig"); -const link = @import("../link.zig"); -const codegen = @import("../codegen/spirv.zig"); -const trace = @import("../tracy.zig").trace; -const build_options = @import("build_options"); -const spec = @import("../codegen/spirv/spec.zig"); - //! SPIR-V Spec documentation: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html //! According to above documentation, a SPIR-V module has the following logical layout: //! Header. @@ -30,6 +16,20 @@ const spec = @import("../codegen/spirv/spec.zig"); //! All function declarations without a body (extern functions presumably). //! All regular functions. +const SpirV = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); +const link = @import("../link.zig"); +const codegen = @import("../codegen/spirv.zig"); +const trace = @import("../tracy.zig").trace; +const build_options = @import("build_options"); +const spec = @import("../codegen/spirv/spec.zig"); + pub const FnData = struct { id: ?u32 = null, code: std.ArrayListUnmanaged(u32) = .{}, @@ -199,7 +199,7 @@ fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void { else => unreachable, // TODO }; - try codegen.writeInstruction(binary, .OpCapability, &[_]u32{ @enumToInt(cap) }); + try codegen.writeInstruction(binary, .OpCapability, &[_]u32{@enumToInt(cap)}); } fn writeMemoryModel(binary: *std.ArrayList(u32), target: std.Target) !void { @@ -221,7 +221,7 @@ fn writeMemoryModel(binary: *std.ArrayList(u32), target: std.Target) !void { }; try codegen.writeInstruction(binary, .OpMemoryModel, &[_]u32{ - @enumToInt(addressing_model), @enumToInt(memory_model) + @enumToInt(addressing_model), @enumToInt(memory_model), }); } diff --git a/src/main.zig b/src/main.zig index 38da3d5a3b..bfac976c5c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3221,8 +3221,7 @@ pub const ClangArgIterator = struct { self.zig_equivalent = clang_arg.zig_equivalent; break :find_clang_arg; }, - } - else { + } else { fatal("Unknown Clang option: '{s}'", .{arg}); } } diff --git a/src/translate_c.zig b/src/translate_c.zig index f2d2f53050..c6d248ab15 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2313,8 +2313,7 @@ fn transCaseStmt(c: *Context, scope: *Scope, stmt: *const clang.Stmt, items: *st const rhs_node = try transExprCoercing(c, scope, rhs, .used); break :blk try Tag.ellipsis3.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node }); - } else - try transExprCoercing(c, scope, case_stmt.getLHS(), .used); + } else try transExprCoercing(c, scope, case_stmt.getLHS(), .used); try items.append(expr); sub = case_stmt.getSubStmt(); @@ -2551,8 +2550,7 @@ fn transArrayAccess(c: *Context, scope: *Scope, stmt: *const clang.ArraySubscrip // check if long long first so that signed long long doesn't just become unsigned long long var typeid_node = if (is_longlong) try Tag.identifier.create(c.arena, "usize") else try transQualTypeIntWidthOf(c, qt, false); break :blk try Tag.int_cast.create(c.arena, .{ .lhs = typeid_node, .rhs = try transExpr(c, scope, subscr_expr, .used) }); - } else - try transExpr(c, scope, subscr_expr, .used); + } else try transExpr(c, scope, subscr_expr, .used); const node = try Tag.array_access.create(c.arena, .{ .lhs = container_node, @@ -2752,8 +2750,7 @@ fn transUnaryOperator(c: *Context, scope: *Scope, stmt: *const clang.UnaryOperat } else if (cIsUnsignedInteger(op_expr.getType())) { // use -% x for unsigned integers return Tag.negate_wrap.create(c.arena, try transExpr(c, scope, op_expr, .used)); - } else - return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "C negation with non float non integer", .{}); + } else return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "C negation with non float non integer", .{}); }, .Not => { return Tag.bit_not.create(c.arena, try transExpr(c, scope, op_expr, .used)); @@ -4593,7 +4590,8 @@ fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!N // (type)alignof(x) .Keyword_alignof, // (type)identifier - .Identifier => {}, + .Identifier, + => {}, // (type)integer .IntegerLiteral => { saw_integer_literal = true; @@ -5068,8 +5066,7 @@ fn getContainerTypeOf(c: *Context, ref: Node) ?Node { return getContainer(c, field.type); } } - } else - return ty_node; + } else return ty_node; } } return null; diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 3bc20271cc..dd837add97 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -312,7 +312,7 @@ pub const Node = extern union { => Payload.Value, .@"if" => Payload.If, .@"while" => Payload.While, - .@"switch", .array_init,.switch_prong => Payload.Switch, + .@"switch", .array_init, .switch_prong => Payload.Switch, .break_val => Payload.BreakVal, .call => Payload.Call, .var_decl => Payload.VarDecl, @@ -394,7 +394,8 @@ pub const Node = extern union { some.data else if (case.castTag(.switch_prong)) |some| some.data.cond - else unreachable; + else + unreachable; if (!body.isNoreturn(break_counts)) return false; } @@ -406,7 +407,6 @@ pub const Node = extern union { } return false; } - }; pub const Payload = struct { -- cgit v1.2.3