From 31a59c229cb39b9ffd1ee3397a1ce87c36b91477 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 26 Jul 2021 19:12:34 -0700 Subject: stage2: improvements towards `zig test` * Add AIR instruction: struct_field_val - This is part of an effort to eliminate the AIR instruction `ref`. - It's implemented for C backend and LLVM backend so far. * Rename `resolvePossiblyUndefinedValue` to `resolveMaybeUndefVal` just to save some columns on long lines. * Sema: add `fieldVal` alongside `fieldPtr` (renamed from `namedFieldPtr`). This is part of an effort to eliminate the AIR instruction `ref`. The idea is to avoid unnecessary loads, stores, stack usage, and IR instructions, by paying a DRY cost. LLVM backend improvements: * internal linkage vs exported linkage is implemented, along with aliases. There is an issue with incremental updates due to missing LLVM API for deleting aliases; see the relevant comment in this commit. - `updateDeclExports` is hooked up to the LLVM backend now. * Fix usage of `Type.tag() == .noreturn` rather than calling `isNoReturn()`. * Properly mark global variables as mutable/constant. * Fix llvm type generation of function pointers * Fix codegen for calls of function pointers * Implement llvm type generation of error unions and error sets. * Implement AIR instructions: addwrap, subwrap, mul, mulwrap, div, bit_and, bool_and, bit_or, bool_or, xor, struct_field_ptr, struct_field_val, unwrap_errunion_err, add for floats, sub for floats. After this commit, `zig test` on a file with `test "example" {}` correctly generates and executes a test binary. However the `test_functions` slice is undefined and just happens to be going into the .bss section, causing the length to be 0. The next step towards `zig test` will be replacing the `test_functions` Decl Value with the set of test function pointers, before it is sent to linker/codegen. --- src/codegen/llvm.zig | 354 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 278 insertions(+), 76 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4224591b0b..1b3c36fc69 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -350,19 +350,21 @@ pub const Object = struct { air: Air, liveness: Liveness, ) !void { + const decl = func.owner_decl; + var dg: DeclGen = .{ .context = self.context, .object = self, .module = module, - .decl = func.owner_decl, + .decl = decl, .err_msg = null, .gpa = module.gpa, }; - const llvm_func = try dg.resolveLLVMFunction(func.owner_decl); + const llvm_func = try dg.resolveLlvmFunction(decl); // This gets the LLVM values from the function and stores them in `dg.args`. - const fn_param_len = func.owner_decl.ty.fnParamLen(); + const fn_param_len = decl.ty.fnParamLen(); var args = try dg.gpa.alloc(*const llvm.Value, fn_param_len); for (args) |*arg, i| { @@ -400,13 +402,16 @@ pub const Object = struct { fg.genBody(air.getMainBody()) catch |err| switch (err) { error.CodegenFail => { - func.owner_decl.analysis = .codegen_failure; - try module.failed_decls.put(module.gpa, func.owner_decl, dg.err_msg.?); + decl.analysis = .codegen_failure; + try module.failed_decls.put(module.gpa, decl, dg.err_msg.?); dg.err_msg = null; return; }, else => |e| return e, }; + + const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; + try self.updateDeclExports(module, decl, decl_exports); } pub fn updateDecl(self: *Object, module: *Module, decl: *Module.Decl) !void { @@ -428,6 +433,38 @@ pub const Object = struct { else => |e| return e, }; } + + pub fn updateDeclExports( + self: *Object, + module: *const Module, + decl: *const Module.Decl, + exports: []const *Module.Export, + ) !void { + const llvm_fn = self.llvm_module.getNamedFunction(decl.name).?; + const is_extern = decl.val.tag() == .extern_fn; + if (is_extern or exports.len != 0) { + llvm_fn.setLinkage(.External); + llvm_fn.setUnnamedAddr(.False); + } else { + llvm_fn.setLinkage(.Internal); + llvm_fn.setUnnamedAddr(.True); + } + // TODO LLVM C API does not support deleting aliases. We need to + // patch it to support this or figure out how to wrap the C++ API ourselves. + // Until then we iterate over existing aliases and make them point + // to the correct decl, or otherwise add a new alias. Old aliases are leaked. + for (exports) |exp| { + if (self.llvm_module.getNamedGlobalAlias(exp.options.name.ptr, exp.options.name.len)) |alias| { + alias.setAliasee(llvm_fn); + } else { + const exp_name_z = try module.gpa.dupeZ(u8, exp.options.name); + defer module.gpa.free(exp_name_z); + + const alias = self.llvm_module.addAlias(llvm_fn.typeOf(), llvm_fn, exp_name_z); + _ = alias; + } + } + } }; pub const DeclGen = struct { @@ -461,21 +498,19 @@ pub const DeclGen = struct { _ = func_payload; @panic("TODO llvm backend genDecl function pointer"); } else if (decl.val.castTag(.extern_fn)) |extern_fn| { - _ = try self.resolveLLVMFunction(extern_fn.data); + _ = try self.resolveLlvmFunction(extern_fn.data); } else { _ = try self.resolveGlobalDecl(decl); } } /// If the llvm function does not exist, create it - fn resolveLLVMFunction(self: *DeclGen, func: *Module.Decl) !*const llvm.Value { - // TODO: do we want to store this in our own datastructure? - if (self.llvmModule().getNamedFunction(func.name)) |llvm_fn| return llvm_fn; + fn resolveLlvmFunction(self: *DeclGen, decl: *Module.Decl) !*const llvm.Value { + if (self.llvmModule().getNamedFunction(decl.name)) |llvm_fn| return llvm_fn; - assert(func.has_tv); - const zig_fn_type = func.ty; + assert(decl.has_tv); + const zig_fn_type = decl.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); @@ -495,9 +530,17 @@ pub const DeclGen = struct { @intCast(c_uint, fn_param_len), .False, ); - const llvm_fn = self.llvmModule().addFunction(func.name, fn_type); + const llvm_fn = self.llvmModule().addFunction(decl.name, fn_type); + + const is_extern = decl.val.tag() == .extern_fn; + if (!is_extern) { + llvm_fn.setLinkage(.Internal); + llvm_fn.setUnnamedAddr(.True); + } + + // TODO: calling convention, linkage, tsan, etc. see codegen.cpp `make_fn_llvm_value`. - if (return_type.tag() == .noreturn) { + if (return_type.isNoReturn()) { self.addFnAttr(llvm_fn, "noreturn"); } @@ -505,7 +548,6 @@ pub const DeclGen = struct { } fn resolveGlobalDecl(self: *DeclGen, decl: *Module.Decl) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - // TODO: do we want to store this in our own datastructure? if (self.llvmModule().getNamedGlobal(decl.name)) |val| return val; assert(decl.has_tv); @@ -515,9 +557,11 @@ pub const DeclGen = struct { const global = self.llvmModule().addGlobal(llvm_type, decl.name); const init_val = if (decl.val.castTag(.variable)) |payload| init_val: { const variable = payload.data; - global.setGlobalConstant(.False); break :init_val variable.init; - } else decl.val; + } else init_val: { + global.setGlobalConstant(.True); + break :init_val decl.val; + }; const llvm_init = try self.genTypedValue(.{ .ty = decl.ty, .val = init_val }, null); llvm.setInitializer(global, llvm_init); @@ -602,12 +646,13 @@ pub const DeclGen = struct { llvm_param.* = try self.llvmType(t.fnParamType(i)); } const is_var_args = t.fnIsVarArgs(); - return llvm.functionType( + const llvm_fn_ty = llvm.functionType( ret_ty, llvm_params.ptr, @intCast(c_uint, llvm_params.len), llvm.Bool.fromBool(is_var_args), ); + return llvm_fn_ty.pointerType(0); }, .ComptimeInt => unreachable, .ComptimeFloat => unreachable, @@ -717,6 +762,42 @@ pub const DeclGen = struct { return self.todo("implement const of optional pointer", .{}); } }, + .Fn => { + const fn_decl = if (tv.val.castTag(.extern_fn)) |extern_fn| + extern_fn.data + else if (tv.val.castTag(.function)) |func_payload| + func_payload.data.owner_decl + else + unreachable; + + return self.resolveLlvmFunction(fn_decl); + }, + .ErrorSet => { + const llvm_ty = try self.llvmType(tv.ty); + switch (tv.val.tag()) { + .@"error" => { + const err_name = tv.val.castTag(.@"error").?.data.name; + const kv = try self.module.getErrorValue(err_name); + return llvm_ty.constInt(kv.value, .False); + }, + else => { + // In this case we are rendering an error union which has a 0 bits payload. + return llvm_ty.constNull(); + }, + } + }, + .ErrorUnion => { + const error_type = tv.ty.errorUnionSet(); + const payload_type = tv.ty.errorUnionPayload(); + const sub_val = tv.val.castTag(.error_union).?.data; + + if (!payload_type.hasCodeGenBits()) { + // We use the error type directly as the type. + return self.genTypedValue(.{ .ty = error_type, .val = sub_val }, fg); + } + + return self.todo("implement error union const of type '{}'", .{tv.ty}); + }, else => return self.todo("implement const of type '{}'", .{tv.ty}), } } @@ -801,8 +882,17 @@ pub const FuncGen = struct { for (body) |inst| { const opt_value: ?*const llvm.Value = switch (air_tags[inst]) { // zig fmt: off - .add => try self.airAdd(inst), - .sub => try self.airSub(inst), + .add => try self.airAdd(inst, false), + .addwrap => try self.airAdd(inst, true), + .sub => try self.airSub(inst, false), + .subwrap => try self.airSub(inst, true), + .mul => try self.airMul(inst, false), + .mulwrap => try self.airMul(inst, true), + .div => try self.airDiv(inst), + + .bit_and, .bool_and => try self.airAnd(inst), + .bit_or, .bool_or => try self.airOr(inst), + .xor => try self.airXor(inst), .cmp_eq => try self.airCmp(inst, .eq), .cmp_gt => try self.airCmp(inst, .gt), @@ -825,10 +915,12 @@ pub const FuncGen = struct { .bitcast => try self.airBitCast(inst), .block => try self.airBlock(inst), .br => try self.airBr(inst), + .switch_br => try self.airSwitchBr(inst), .breakpoint => try self.airBreakpoint(inst), .call => try self.airCall(inst), .cond_br => try self.airCondBr(inst), .intcast => try self.airIntCast(inst), + .floatcast => try self.airFloatCast(inst), .ptrtoint => try self.airPtrToInt(inst), .load => try self.airLoad(inst), .loop => try self.airLoop(inst), @@ -840,6 +932,9 @@ pub const FuncGen = struct { .slice_ptr => try self.airSliceField(inst, 0), .slice_len => try self.airSliceField(inst, 1), + .struct_field_ptr => try self.airStructFieldPtr(inst), + .struct_field_val => try self.airStructFieldVal(inst), + .slice_elem_val => try self.airSliceElemVal(inst, false), .ptr_slice_elem_val => try self.airSliceElemVal(inst, true), @@ -851,12 +946,18 @@ pub const FuncGen = struct { .unwrap_errunion_err => try self.airErrUnionErr(inst, false), .unwrap_errunion_err_ptr => try self.airErrUnionErr(inst, true), + .wrap_optional => try self.airWrapOptional(inst), + .wrap_errunion_payload => try self.airWrapErrUnionPayload(inst), + .wrap_errunion_err => try self.airWrapErrUnionErr(inst), + + .constant => unreachable, + .const_ty => unreachable, + .ref => unreachable, // TODO eradicate this instruction .unreach => self.airUnreach(inst), .dbg_stmt => blk: { // TODO: implement debug info break :blk null; }, - else => |tag| return self.todo("implement AIR instruction: {}", .{tag}), // zig fmt: on }; if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val); @@ -867,47 +968,32 @@ pub const FuncGen = struct { const pl_op = self.air.instructions.items(.data)[inst].pl_op; const extra = self.air.extraData(Air.Call, pl_op.payload); const args = @bitCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]); + const zig_fn_type = self.air.typeOf(pl_op.operand); + const return_type = zig_fn_type.fnReturnType(); + const llvm_fn = try self.resolveInst(pl_op.operand); - if (self.air.value(pl_op.operand)) |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; - - assert(fn_decl.has_tv); - const zig_fn_type = fn_decl.ty; - const llvm_fn = try self.dg.resolveLLVMFunction(fn_decl); + const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, args.len); + defer self.gpa.free(llvm_param_vals); - const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, args.len); - defer self.gpa.free(llvm_param_vals); + for (args) |arg, i| { + llvm_param_vals[i] = try self.resolveInst(arg); + } - for (args) |arg, i| { - llvm_param_vals[i] = try self.resolveInst(arg); - } + const call = self.builder.buildCall( + llvm_fn, + llvm_param_vals.ptr, + @intCast(c_uint, args.len), + "", + ); - // TODO: LLVMBuildCall2 handles opaque function pointers, according to llvm docs - // Do we need that? - const call = self.builder.buildCall( - llvm_fn, - llvm_param_vals.ptr, - @intCast(c_uint, args.len), - "", - ); - - const return_type = zig_fn_type.fnReturnType(); - if (return_type.tag() == .noreturn) { - _ = self.builder.buildUnreachable(); - } + if (return_type.isNoReturn()) { + _ = self.builder.buildUnreachable(); + } - // No need to store the LLVM value if the return type is void or noreturn - if (!return_type.hasCodeGenBits()) return null; + // 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.todo("implement calling runtime known function pointer", .{}); - } + return call; } fn airRet(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -1026,6 +1112,11 @@ pub const FuncGen = struct { return null; } + fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + _ = inst; + return self.todo("implement llvm codegen for switch_br", .{}); + } + fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const loop = self.air.extraData(Air.Block, ty_pl.payload); @@ -1082,10 +1173,32 @@ pub const FuncGen = struct { }; const indices: [1]*const llvm.Value = .{rhs}; - const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, 1, ""); + const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); return self.builder.buildLoad(ptr, ""); } + fn airStructFieldPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; + const struct_ptr = try self.resolveInst(struct_field.struct_operand); + const field_index = @intCast(c_uint, struct_field.field_index); + return self.builder.buildStructGEP(struct_ptr, field_index, ""); + } + + fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; + const struct_byval = try self.resolveInst(struct_field.struct_operand); + const field_index = @intCast(c_uint, struct_field.field_index); + return self.builder.buildExtractValue(struct_byval, field_index, ""); + } + fn airNot(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1321,7 +1434,7 @@ pub const FuncGen = struct { _ = operand; _ = operand_is_ptr; - return self.todo("implement 'airErrUnionPayload' for type {}", .{self.air.typeOf(ty_op.operand)}); + return self.todo("implement llvm codegen for 'airErrUnionPayload' for type {}", .{self.air.typeOf(ty_op.operand)}); } fn airErrUnionErr( @@ -1332,42 +1445,123 @@ pub const FuncGen = struct { if (self.liveness.isUnused(inst)) return null; - _ = operand_is_ptr; - return self.todo("implement 'airErrUnionErr'", .{}); + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const operand_ty = self.air.typeOf(ty_op.operand); + + const payload_ty = operand_ty.errorUnionPayload(); + if (!payload_ty.hasCodeGenBits()) { + if (!operand_is_ptr) return operand; + return self.builder.buildLoad(operand, ""); + } + return self.todo("implement llvm codegen for 'airErrUnionErr'", .{}); + } + + fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + return self.todo("implement llvm codegen for 'airWrapOptional'", .{}); + } + + fn airWrapErrUnionPayload(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + return self.todo("implement llvm codegen for 'airWrapErrUnionPayload'", .{}); + } + + fn airWrapErrUnionErr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{}); } - fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airAdd(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (!inst_ty.isInt()) - return self.todo("implement 'airAdd' for type {}", .{inst_ty}); + if (inst_ty.isFloat()) return self.builder.buildFAdd(lhs, rhs, ""); + if (wrap) return self.builder.buildAdd(lhs, rhs, ""); + if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, ""); + return self.builder.buildNUWAdd(lhs, rhs, ""); + } - return if (inst_ty.isSignedInt()) - self.builder.buildNSWAdd(lhs, rhs, "") - else - self.builder.buildNUWAdd(lhs, rhs, ""); + fn airSub(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const inst_ty = self.air.typeOfIndex(inst); + + if (inst_ty.isFloat()) return self.builder.buildFSub(lhs, rhs, ""); + if (wrap) return self.builder.buildSub(lhs, rhs, ""); + if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, ""); + return self.builder.buildNUWSub(lhs, rhs, ""); } - fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airMul(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; + const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (!inst_ty.isInt()) - return self.todo("implement 'airSub' for type {}", .{inst_ty}); + if (inst_ty.isFloat()) return self.builder.buildFMul(lhs, rhs, ""); + if (wrap) return self.builder.buildMul(lhs, rhs, ""); + if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, ""); + return self.builder.buildNUWMul(lhs, rhs, ""); + } - return if (inst_ty.isSignedInt()) - self.builder.buildNSWSub(lhs, rhs, "") - else - self.builder.buildNUWSub(lhs, rhs, ""); + fn airDiv(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const inst_ty = self.air.typeOfIndex(inst); + + if (inst_ty.isFloat()) return self.builder.buildFDiv(lhs, rhs, ""); + if (inst_ty.isSignedInt()) return self.builder.buildSDiv(lhs, rhs, ""); + return self.builder.buildUDiv(lhs, rhs, ""); + } + + fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + return self.builder.buildAnd(lhs, rhs, ""); + } + + fn airOr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + return self.builder.buildOr(lhs, rhs, ""); + } + + fn airXor(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + return self.builder.buildXor(lhs, rhs, ""); } fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -1384,6 +1578,14 @@ pub const FuncGen = struct { return self.builder.buildIntCast2(operand, try self.dg.llvmType(inst_ty), llvm.Bool.fromBool(signed), ""); } + fn airFloatCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + // TODO split floatcast AIR into float_widen and float_shorten + return self.todo("implement 'airFloatCast'", .{}); + } + fn airPtrToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1474,8 +1676,8 @@ pub const FuncGen = struct { fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { _ = inst; - const llvn_fn = self.getIntrinsic("llvm.debugtrap"); - _ = self.builder.buildCall(llvn_fn, undefined, 0, ""); + const llvm_fn = self.getIntrinsic("llvm.debugtrap"); + _ = self.builder.buildCall(llvm_fn, undefined, 0, ""); return null; } -- cgit v1.2.3 From a8e964eadd3496330043985cacaaee7db92886c6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jul 2021 14:06:42 -0700 Subject: stage2: `zig test` now works with the LLVM backend Frontend improvements: * When compiling in `zig test` mode, put a task on the work queue to analyze the main package root file. Normally, start code does `_ = import("root");` to make Zig analyze the user's code, however in the case of `zig test`, the root source file is the test runner. Without this change, no tests are picked up. * In the main pipeline, once semantic analysis is finished, if there are no compile errors, populate the `test_functions` Decl with the set of test functions picked up from semantic analysis. * Value: add `array` and `slice` Tags. LLVM backend improvements: * Fix incremental updates of globals. Previously the value of a global would not get replaced with a new value. * Fix LLVM type of arrays. They were incorrectly sending the ABI size as the element count. * Remove the FuncGen parameter from genTypedValue. This function is for generating global constants and there is no function available when it is being called. - The `ref_val` case is now commented out. I'd like to eliminate `ref_val` as one of the possible Value Tags. Instead it should always be done via `decl_ref`. * Implement constant value generation for slices, arrays, and structs. * Constant value generation for functions supports the `decl_ref` tag. --- src/Compilation.zig | 34 +++++------ src/Module.zig | 135 ++++++++++++++++++++++++++++++++++++++---- src/codegen/llvm.zig | 134 +++++++++++++++++++++++++++-------------- src/codegen/llvm/bindings.zig | 16 ++++- src/type.zig | 22 +++++++ src/value.zig | 73 +++++++++++++++++++++-- 6 files changed, 332 insertions(+), 82 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index a34d0cd198..00cdba85a4 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1709,7 +1709,9 @@ pub fn update(self: *Compilation) !void { // in the start code, but when using the stage1 backend that won't happen, // so in order to run AstGen on the root source file we put it into the // import_table here. - if (use_stage1) { + // Likewise, in the case of `zig test`, the test runner is the root source file, + // and so there is nothing to import the main file. + if (use_stage1 or self.bin_file.options.is_test) { _ = try module.importPkg(module.main_pkg); } @@ -1725,6 +1727,9 @@ pub fn update(self: *Compilation) !void { if (!use_stage1) { try self.work_queue.writeItem(.{ .analyze_pkg = std_pkg }); + if (self.bin_file.options.is_test) { + try self.work_queue.writeItem(.{ .analyze_pkg = module.main_pkg }); + } } } @@ -2053,24 +2058,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor assert(decl.has_tv); assert(decl.ty.hasCodeGenBits()); - 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.ensureUnusedCapacity(gpa, 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create( - gpa, - decl.srcLoc(), - "unable to codegen: {s}", - .{@errorName(err)}, - )); - decl.analysis = .codegen_failure_retryable; - continue; - }, - }; + try module.linkerUpdateDecl(decl); }, }, .codegen_func => |func| switch (func.owner_decl.analysis) { @@ -2396,6 +2384,14 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor }; }, }; + + if (self.bin_file.options.is_test and self.totalErrorCount() == 0) { + // The `test_functions` decl has been intentionally postponed until now, + // at which point we must populate it with the list of test functions that + // have been discovered and not filtered out. + const mod = self.bin_file.options.module.?; + try mod.populateTestFunctions(); + } } const AstGenSrc = union(enum) { diff --git a/src/Module.zig b/src/Module.zig index 99f314c5cb..4ddce33655 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -112,6 +112,8 @@ compile_log_text: ArrayListUnmanaged(u8) = .{}, emit_h: ?*GlobalEmitH, +test_functions: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{}, + /// A `Module` has zero or one of these depending on whether `-femit-h` is enabled. pub const GlobalEmitH = struct { /// Where to put the output. @@ -282,6 +284,7 @@ pub const Decl = struct { pub fn destroy(decl: *Decl, module: *Module) void { const gpa = module.gpa; log.debug("destroy {*} ({s})", .{ decl, decl.name }); + _ = module.test_functions.swapRemove(decl); if (decl.deletion_flag) { assert(module.deletion_set.swapRemove(decl)); } @@ -3319,6 +3322,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi // the test name filter. if (!mod.comp.bin_file.options.is_test) break :blk false; if (decl_pkg != mod.main_pkg) break :blk false; + try mod.test_functions.put(gpa, new_decl, {}); break :blk true; }, else => blk: { @@ -3326,6 +3330,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi if (!mod.comp.bin_file.options.is_test) break :blk false; if (decl_pkg != mod.main_pkg) break :blk false; // TODO check the name against --test-filter + try mod.test_functions.put(gpa, new_decl, {}); break :blk true; }, }; @@ -3765,17 +3770,38 @@ pub fn createAnonymousDeclNamed( scope: *Scope, typed_value: TypedValue, name: [:0]u8, +) !*Decl { + return mod.createAnonymousDeclFromDeclNamed(scope.ownerDecl().?, typed_value, name); +} + +pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl { + return mod.createAnonymousDeclFromDecl(scope.ownerDecl().?, typed_value); +} + +pub fn createAnonymousDeclFromDecl(mod: *Module, owner_decl: *Decl, tv: TypedValue) !*Decl { + const name_index = mod.getNextAnonNameIndex(); + const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{ + owner_decl.name, name_index, + }); + return mod.createAnonymousDeclFromDeclNamed(owner_decl, tv, name); +} + +/// Takes ownership of `name` even if it returns an error. +pub fn createAnonymousDeclFromDeclNamed( + mod: *Module, + owner_decl: *Decl, + typed_value: TypedValue, + name: [:0]u8, ) !*Decl { errdefer mod.gpa.free(name); - const scope_decl = scope.ownerDecl().?; - const namespace = scope_decl.namespace; + const namespace = owner_decl.namespace; try namespace.anon_decls.ensureUnusedCapacity(mod.gpa, 1); - const new_decl = try mod.allocateNewDecl(namespace, scope_decl.src_node); + const new_decl = try mod.allocateNewDecl(namespace, owner_decl.src_node); new_decl.name = name; - new_decl.src_line = scope_decl.src_line; + new_decl.src_line = owner_decl.src_line; new_decl.ty = typed_value.ty; new_decl.val = typed_value.val; new_decl.has_tv = true; @@ -3796,15 +3822,6 @@ pub fn createAnonymousDeclNamed( return new_decl; } -pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl { - const scope_decl = scope.ownerDecl().?; - const name_index = mod.getNextAnonNameIndex(); - const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{ - scope_decl.name, name_index, - }); - return mod.createAnonymousDeclNamed(scope, typed_value, name); -} - pub fn getNextAnonNameIndex(mod: *Module) usize { return @atomicRmw(usize, &mod.next_anon_name_index, .Add, 1, .Monotonic); } @@ -4801,3 +4818,95 @@ pub fn processExports(mod: *Module) !void { }; } } + +pub fn populateTestFunctions(mod: *Module) !void { + const gpa = mod.gpa; + const builtin_pkg = mod.main_pkg.table.get("builtin").?; + const builtin_file = (mod.importPkg(builtin_pkg) catch unreachable).file; + const builtin_namespace = builtin_file.root_decl.?.namespace; + const decl = builtin_namespace.decls.get("test_functions").?; + var buf: Type.Payload.ElemType = undefined; + const tmp_test_fn_ty = decl.ty.slicePtrFieldType(&buf).elemType(); + + const array_decl = d: { + // Add mod.test_functions to an array decl then make the test_functions + // decl reference it as a slice. + var new_decl_arena = std.heap.ArenaAllocator.init(gpa); + errdefer new_decl_arena.deinit(); + const arena = &new_decl_arena.allocator; + + const test_fn_vals = try arena.alloc(Value, mod.test_functions.count()); + const array_decl = try mod.createAnonymousDeclFromDecl(decl, .{ + .ty = try Type.Tag.array.create(arena, .{ + .len = test_fn_vals.len, + .elem_type = try tmp_test_fn_ty.copy(arena), + }), + .val = try Value.Tag.array.create(arena, test_fn_vals), + }); + for (mod.test_functions.keys()) |test_decl, i| { + const test_name_slice = mem.sliceTo(test_decl.name, 0); + const test_name_decl = n: { + var name_decl_arena = std.heap.ArenaAllocator.init(gpa); + errdefer name_decl_arena.deinit(); + const bytes = try name_decl_arena.allocator.dupe(u8, test_name_slice); + const test_name_decl = try mod.createAnonymousDeclFromDecl(array_decl, .{ + .ty = try Type.Tag.array_u8.create(&name_decl_arena.allocator, bytes.len), + .val = try Value.Tag.bytes.create(&name_decl_arena.allocator, bytes), + }); + try test_name_decl.finalizeNewArena(&name_decl_arena); + break :n test_name_decl; + }; + try mod.linkerUpdateDecl(test_name_decl); + + const field_vals = try arena.create([3]Value); + field_vals.* = .{ + try Value.Tag.slice.create(arena, .{ + .ptr = try Value.Tag.decl_ref.create(arena, test_name_decl), + .len = try Value.Tag.int_u64.create(arena, test_name_slice.len), + }), // name + try Value.Tag.decl_ref.create(arena, test_decl), // func + Value.initTag(.null_value), // async_frame_size + }; + test_fn_vals[i] = try Value.Tag.@"struct".create(arena, field_vals); + } + + try array_decl.finalizeNewArena(&new_decl_arena); + break :d array_decl; + }; + try mod.linkerUpdateDecl(array_decl); + + { + var arena_instance = decl.value_arena.?.promote(gpa); + defer decl.value_arena.?.* = arena_instance.state; + const arena = &arena_instance.allocator; + + decl.ty = try Type.Tag.const_slice.create(arena, try tmp_test_fn_ty.copy(arena)); + decl.val = try Value.Tag.slice.create(arena, .{ + .ptr = try Value.Tag.decl_ref.create(arena, array_decl), + .len = try Value.Tag.int_u64.create(arena, mod.test_functions.count()), + }); + } + try mod.linkerUpdateDecl(decl); +} + +pub fn linkerUpdateDecl(mod: *Module, decl: *Decl) !void { + mod.comp.bin_file.updateDecl(mod, decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + decl.analysis = .codegen_failure; + return; + }, + else => { + const gpa = mod.gpa; + try mod.failed_decls.ensureUnusedCapacity(gpa, 1); + mod.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + gpa, + decl.srcLoc(), + "unable to codegen: {s}", + .{@errorName(err)}, + )); + decl.analysis = .codegen_failure_retryable; + return; + }, + }; +} diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 1b3c36fc69..2468b1dbde 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -500,7 +500,18 @@ pub const DeclGen = struct { } else if (decl.val.castTag(.extern_fn)) |extern_fn| { _ = try self.resolveLlvmFunction(extern_fn.data); } else { - _ = try self.resolveGlobalDecl(decl); + const global = try self.resolveGlobalDecl(decl); + assert(decl.has_tv); + const init_val = if (decl.val.castTag(.variable)) |payload| init_val: { + const variable = payload.data; + break :init_val variable.init; + } else init_val: { + global.setGlobalConstant(.True); + break :init_val decl.val; + }; + + const llvm_init = try self.genTypedValue(.{ .ty = decl.ty, .val = init_val }); + llvm.setInitializer(global, llvm_init); } } @@ -548,25 +559,11 @@ pub const DeclGen = struct { } fn resolveGlobalDecl(self: *DeclGen, decl: *Module.Decl) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - if (self.llvmModule().getNamedGlobal(decl.name)) |val| return val; - - assert(decl.has_tv); - + const llvm_module = self.object.llvm_module; + if (llvm_module.getNamedGlobal(decl.name)) |val| return val; // TODO: remove this redundant `llvmType`, it is also called in `genTypedValue`. const llvm_type = try self.llvmType(decl.ty); - const global = self.llvmModule().addGlobal(llvm_type, decl.name); - const init_val = if (decl.val.castTag(.variable)) |payload| init_val: { - const variable = payload.data; - break :init_val variable.init; - } else init_val: { - global.setGlobalConstant(.True); - break :init_val decl.val; - }; - - const llvm_init = try self.genTypedValue(.{ .ty = decl.ty, .val = init_val }, null); - llvm.setInitializer(global, llvm_init); - - return global; + return llvm_module.addGlobal(llvm_type, decl.name); } fn llvmType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type { @@ -596,7 +593,8 @@ pub const DeclGen = struct { }, .Array => { const elem_type = try self.llvmType(t.elemType()); - return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget()))); + const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null); + return elem_type.arrayType(@intCast(c_uint, total_len)); }, .Optional => { if (!t.isPtrLikeOptional()) { @@ -674,8 +672,7 @@ pub const DeclGen = struct { } } - // TODO: figure out a way to remove the FuncGen argument - fn genTypedValue(self: *DeclGen, tv: TypedValue, fg: ?*FuncGen) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + fn genTypedValue(self: *DeclGen, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { const llvm_type = try self.llvmType(tv.ty); if (tv.val.isUndef()) @@ -711,20 +708,36 @@ pub const DeclGen = struct { usize_type.constNull(), }; - // TODO: consider using buildInBoundsGEP2 for opaque pointers - return fg.?.builder.buildInBoundsGEP(val, &indices, 2, ""); + return val.constInBoundsGEP(&indices, indices.len); }, .ref_val => { - const elem_value = tv.val.castTag(.ref_val).?.data; - const elem_type = tv.ty.castPointer().?.data; - const alloca = fg.?.buildAlloca(try self.llvmType(elem_type)); - _ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca); - return alloca; + //const elem_value = tv.val.castTag(.ref_val).?.data; + //const elem_type = tv.ty.castPointer().?.data; + //const alloca = fg.?.buildAlloca(try self.llvmType(elem_type)); + //_ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca); + //return alloca; + // TODO eliminate the ref_val Value Tag + return self.todo("implement const of pointer tag ref_val", .{}); }, .variable => { const variable = tv.val.castTag(.variable).?.data; return self.resolveGlobalDecl(variable.owner_decl); }, + .slice => { + const slice = tv.val.castTag(.slice).?.data; + var buf: Type.Payload.ElemType = undefined; + const fields: [2]*const llvm.Value = .{ + try self.genTypedValue(.{ + .ty = tv.ty.slicePtrFieldType(&buf), + .val = slice.ptr, + }), + try self.genTypedValue(.{ + .ty = Type.initTag(.usize), + .val = slice.len, + }), + }; + return self.context.constStruct(&fields, fields.len, .False); + }, else => |tag| return self.todo("implement const of pointer type '{}' ({})", .{ tv.ty, tag }), }, .Array => { @@ -734,10 +747,28 @@ pub const DeclGen = struct { return self.todo("handle other sentinel values", .{}); } else false; - return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), llvm.Bool.fromBool(!zero_sentinel)); - } else { - return self.todo("handle more array values", .{}); + return self.context.constString( + payload.data.ptr, + @intCast(c_uint, payload.data.len), + llvm.Bool.fromBool(!zero_sentinel), + ); + } + if (tv.val.castTag(.array)) |payload| { + const gpa = self.gpa; + const elem_ty = tv.ty.elemType(); + const elem_vals = payload.data; + const llvm_elems = try gpa.alloc(*const llvm.Value, elem_vals.len); + defer gpa.free(llvm_elems); + for (elem_vals) |elem_val, i| { + llvm_elems[i] = try self.genTypedValue(.{ .ty = elem_ty, .val = elem_val }); + } + const llvm_elem_ty = try self.llvmType(elem_ty); + return llvm_elem_ty.constArray( + llvm_elems.ptr, + @intCast(c_uint, llvm_elems.len), + ); } + return self.todo("handle more array values", .{}); }, .Optional => { if (!tv.ty.isPtrLikeOptional()) { @@ -750,26 +781,25 @@ pub const DeclGen = struct { llvm_child_type.constNull(), self.context.intType(1).constNull(), }; - return self.context.constStruct(&optional_values, 2, .False); + return self.context.constStruct(&optional_values, optional_values.len, .False); } else { var optional_values: [2]*const llvm.Value = .{ - try self.genTypedValue(.{ .ty = child_type, .val = tv.val }, fg), + try self.genTypedValue(.{ .ty = child_type, .val = tv.val }), self.context.intType(1).constAllOnes(), }; - return self.context.constStruct(&optional_values, 2, .False); + return self.context.constStruct(&optional_values, optional_values.len, .False); } } else { return self.todo("implement const of optional pointer", .{}); } }, .Fn => { - const fn_decl = if (tv.val.castTag(.extern_fn)) |extern_fn| - extern_fn.data - else if (tv.val.castTag(.function)) |func_payload| - func_payload.data.owner_decl - else - unreachable; - + const fn_decl = switch (tv.val.tag()) { + .extern_fn => tv.val.castTag(.extern_fn).?.data, + .function => tv.val.castTag(.function).?.data.owner_decl, + .decl_ref => tv.val.castTag(.decl_ref).?.data, + else => unreachable, + }; return self.resolveLlvmFunction(fn_decl); }, .ErrorSet => { @@ -793,11 +823,29 @@ pub const DeclGen = struct { if (!payload_type.hasCodeGenBits()) { // We use the error type directly as the type. - return self.genTypedValue(.{ .ty = error_type, .val = sub_val }, fg); + return self.genTypedValue(.{ .ty = error_type, .val = sub_val }); } return self.todo("implement error union const of type '{}'", .{tv.ty}); }, + .Struct => { + const fields_len = tv.ty.structFieldCount(); + const field_vals = tv.val.castTag(.@"struct").?.data; + const gpa = self.gpa; + const llvm_fields = try gpa.alloc(*const llvm.Value, fields_len); + defer gpa.free(llvm_fields); + for (llvm_fields) |*llvm_field, i| { + llvm_field.* = try self.genTypedValue(.{ + .ty = tv.ty.structFieldType(i), + .val = field_vals[i], + }); + } + return self.context.constStruct( + llvm_fields.ptr, + @intCast(c_uint, llvm_fields.len), + .False, + ); + }, else => return self.todo("implement const of type '{}'", .{tv.ty}), } } @@ -869,7 +917,7 @@ pub const FuncGen = struct { fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*const llvm.Value { if (self.air.value(inst)) |val| { - return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }, self); + return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }); } const inst_index = Air.refToIndex(inst).?; if (self.func_inst_table.get(inst_index)) |value| return value; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 9ef6d7e1ac..d63a75d8bd 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -49,7 +49,12 @@ pub const Context = opaque { extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: Bool) *const Value; pub const constStruct = LLVMConstStructInContext; - extern fn LLVMConstStructInContext(C: *const Context, ConstantVals: [*]*const Value, Count: c_uint, Packed: Bool) *const Value; + extern fn LLVMConstStructInContext( + C: *const Context, + ConstantVals: [*]const *const Value, + Count: c_uint, + Packed: Bool, + ) *const Value; pub const createBasicBlock = LLVMCreateBasicBlockInContext; extern fn LLVMCreateBasicBlockInContext(C: *const Context, Name: [*:0]const u8) *const BasicBlock; @@ -100,6 +105,13 @@ pub const Value = opaque { pub const setAliasee = LLVMAliasSetAliasee; extern fn LLVMAliasSetAliasee(Alias: *const Value, Aliasee: *const Value) void; + + pub const constInBoundsGEP = LLVMConstInBoundsGEP; + extern fn LLVMConstInBoundsGEP( + ConstantVal: *const Value, + ConstantIndices: [*]const *const Value, + NumIndices: c_uint, + ) *const Value; }; pub const Type = opaque { @@ -113,7 +125,7 @@ pub const Type = opaque { extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value; pub const constArray = LLVMConstArray; - extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: ?[*]*const Value, Length: c_uint) *const Value; + 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; diff --git a/src/type.zig b/src/type.zig index 731c69b806..4dd1a15fdd 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1526,6 +1526,8 @@ pub const Type = extern union { .var_args_param => unreachable, .@"struct" => { + const s = self.castTag(.@"struct").?.data; + assert(s.status == .have_layout); @panic("TODO abiSize struct"); }, .enum_simple, .enum_full, .enum_nonexhaustive => { @@ -2768,6 +2770,26 @@ pub const Type = extern union { } } + pub fn structFieldCount(ty: Type) usize { + switch (ty.tag()) { + .@"struct" => { + const struct_obj = ty.castTag(.@"struct").?.data; + return struct_obj.fields.count(); + }, + else => unreachable, + } + } + + pub fn structFieldType(ty: Type, index: usize) Type { + switch (ty.tag()) { + .@"struct" => { + const struct_obj = ty.castTag(.@"struct").?.data; + return struct_obj.fields.values()[index].ty; + }, + else => unreachable, + } + } + pub fn declSrcLoc(ty: Type) Module.SrcLoc { switch (ty.tag()) { .enum_full, .enum_nonexhaustive => { diff --git a/src/value.zig b/src/value.zig index abb2ea7b1e..fc34473921 100644 --- a/src/value.zig +++ b/src/value.zig @@ -112,6 +112,10 @@ pub const Value = extern union { /// This value is repeated some number of times. The amount of times to repeat /// is stored externally. repeated, + /// Each element stored as a `Value`. + array, + /// Pointer and length as sub `Value` objects. + slice, float_16, float_32, float_64, @@ -217,6 +221,9 @@ pub const Value = extern union { .enum_literal, => Payload.Bytes, + .array => Payload.Array, + .slice => Payload.Slice, + .enum_field_index => Payload.U32, .ty => Payload.Ty, @@ -442,6 +449,28 @@ pub const Value = extern union { }; return Value{ .ptr_otherwise = &new_payload.base }; }, + .array => { + const payload = self.castTag(.array).?; + const new_payload = try allocator.create(Payload.Array); + new_payload.* = .{ + .base = payload.base, + .data = try allocator.alloc(Value, payload.data.len), + }; + std.mem.copy(Value, new_payload.data, payload.data); + return Value{ .ptr_otherwise = &new_payload.base }; + }, + .slice => { + const payload = self.castTag(.slice).?; + const new_payload = try allocator.create(Payload.Slice); + new_payload.* = .{ + .base = payload.base, + .data = .{ + .ptr = try payload.data.ptr.copy(allocator), + .len = try payload.data.len.copy(allocator), + }, + }; + return Value{ .ptr_otherwise = &new_payload.base }; + }, .float_16 => return self.copyPayloadShallow(allocator, Payload.Float_16), .float_32 => return self.copyPayloadShallow(allocator, Payload.Float_32), .float_64 => return self.copyPayloadShallow(allocator, Payload.Float_64), @@ -605,6 +634,8 @@ pub const Value = extern union { try out_stream.writeAll("(repeated) "); val = val.castTag(.repeated).?.data; }, + .array => return out_stream.writeAll("(array)"), + .slice => return out_stream.writeAll("(slice)"), .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}), @@ -729,6 +760,8 @@ pub const Value = extern union { .field_ptr, .bytes, .repeated, + .array, + .slice, .float_16, .float_32, .float_64, @@ -1075,6 +1108,8 @@ pub const Value = extern union { return orderAgainstZero(lhs).compare(op); } + /// TODO we can't compare value equality without also knowing the type to treat + /// the values as pub fn eql(a: Value, b: Value) bool { const a_tag = a.tag(); const b_tag = b.tag(); @@ -1109,6 +1144,8 @@ pub const Value = extern union { return @truncate(u32, self.hash()); } + /// TODO we can't hash without also knowing the type of the value. + /// we have to hash as if there were a canonical value memory layout. pub fn hash(self: Value) u64 { var hasher = std.hash.Wyhash.init(0); @@ -1203,6 +1240,15 @@ pub const Value = extern union { const payload = self.castTag(.bytes).?; hasher.update(payload.data); }, + .repeated => { + @panic("TODO Value.hash for repeated"); + }, + .array => { + @panic("TODO Value.hash for array"); + }, + .slice => { + @panic("TODO Value.hash for slice"); + }, .int_u64 => { const payload = self.castTag(.int_u64).?; std.hash.autoHash(&hasher, payload.data); @@ -1211,10 +1257,6 @@ pub const Value = extern union { const payload = self.castTag(.int_i64).?; std.hash.autoHash(&hasher, payload.data); }, - .repeated => { - const payload = self.castTag(.repeated).?; - std.hash.autoHash(&hasher, payload.data.hash()); - }, .ref_val => { const payload = self.castTag(.ref_val).?; std.hash.autoHash(&hasher, payload.data.hash()); @@ -1340,6 +1382,8 @@ pub const Value = extern union { return switch (val.tag()) { .empty_array => 0, .bytes => val.castTag(.bytes).?.data.len, + .array => val.castTag(.array).?.data.len, + .slice => val.castTag(.slice).?.data.len.toUnsignedInt(), .ref_val => sliceLen(val.castTag(.ref_val).?.data), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; @@ -1364,6 +1408,9 @@ pub const Value = extern union { // No matter the index; all the elements are the same! .repeated => return self.castTag(.repeated).?.data, + .array => return self.castTag(.array).?.data[index], + .slice => return self.castTag(.slice).?.data.ptr.elemValue(allocator, index), + else => unreachable, } } @@ -1450,7 +1497,8 @@ pub const Value = extern union { } /// Valid for all types. Asserts the value is not undefined. - pub fn isType(self: Value) bool { + /// TODO this function is a code smell and should be deleted + fn isType(self: Value) bool { return switch (self.tag()) { .ty, .int_type, @@ -1528,6 +1576,8 @@ pub const Value = extern union { .field_ptr, .bytes, .repeated, + .array, + .slice, .float_16, .float_32, .float_64, @@ -1638,6 +1688,19 @@ pub const Value = extern union { data: []const u8, }; + pub const Array = struct { + base: Payload, + data: []Value, + }; + + pub const Slice = struct { + base: Payload, + data: struct { + ptr: Value, + len: Value, + }, + }; + pub const Ty = struct { base: Payload, data: Type, -- cgit v1.2.3 From 66e5920dc3411daa4f0c84a8f4c733c1263e8523 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jul 2021 15:47:25 -0700 Subject: llvm backend: LLVMGetNamedGlobalAlias requires a null terminated string --- src/codegen/llvm.zig | 8 ++++---- src/codegen/llvm/bindings.zig | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 2468b1dbde..74a51e6634 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -454,12 +454,12 @@ pub const Object = struct { // Until then we iterate over existing aliases and make them point // to the correct decl, or otherwise add a new alias. Old aliases are leaked. for (exports) |exp| { - if (self.llvm_module.getNamedGlobalAlias(exp.options.name.ptr, exp.options.name.len)) |alias| { + const exp_name_z = try module.gpa.dupeZ(u8, exp.options.name); + defer module.gpa.free(exp_name_z); + + if (self.llvm_module.getNamedGlobalAlias(exp_name_z.ptr, exp_name_z.len)) |alias| { alias.setAliasee(llvm_fn); } else { - const exp_name_z = try module.gpa.dupeZ(u8, exp.options.name); - defer module.gpa.free(exp_name_z); - const alias = self.llvm_module.addAlias(llvm_fn.typeOf(), llvm_fn, exp_name_z); _ = alias; } diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index d63a75d8bd..6d09d69447 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -193,7 +193,10 @@ pub const Module = opaque { pub const getNamedGlobalAlias = LLVMGetNamedGlobalAlias; extern fn LLVMGetNamedGlobalAlias( M: *const Module, - Name: [*]const u8, + /// Empirically, LLVM will call strlen() on `Name` and so it + /// must be both null terminated and also have `NameLen` set + /// to the size. + Name: [*:0]const u8, NameLen: usize, ) ?*const Value; }; -- cgit v1.2.3 From dc88864c9742029c2980fc16cd2c9e6f04ff3568 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jul 2021 17:08:37 -0700 Subject: stage2: implement `@boolToInt` This is the first commit in which some behavior tests are passing for both stage1 and stage2. --- doc/langref.html.in | 4 ++-- src/Air.zig | 6 ++++++ src/AstGen.zig | 2 ++ src/Liveness.zig | 1 + src/Sema.zig | 11 +++++++++-- src/Zir.zig | 5 +++++ src/codegen.zig | 8 ++++++++ src/codegen/c.zig | 15 +++++++++++++++ src/codegen/llvm.zig | 10 ++++++++++ src/link/Coff.zig | 7 +++++-- src/link/Elf.zig | 7 ++++--- src/print_air.zig | 1 + src/type.zig | 17 +++++++++++++++-- src/value.zig | 7 +++++++ test/behavior.zig | 7 ++----- 15 files changed, 92 insertions(+), 16 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/doc/langref.html.in b/doc/langref.html.in index 38b91468e2..4efa7d0e3c 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7165,8 +7165,8 @@ fn func(y: *i32) void { {#header_open|@boolToInt#}
{#syntax#}@boolToInt(value: bool) u1{#endsyntax#}

- Converts {#syntax#}true{#endsyntax#} to {#syntax#}u1(1){#endsyntax#} and {#syntax#}false{#endsyntax#} to - {#syntax#}u1(0){#endsyntax#}. + Converts {#syntax#}true{#endsyntax#} to {#syntax#}@as(u1, 1){#endsyntax#} and {#syntax#}false{#endsyntax#} to + {#syntax#}@as(u1, 0){#endsyntax#}.

If the value is known at compile-time, the return type is {#syntax#}comptime_int{#endsyntax#} diff --git a/src/Air.zig b/src/Air.zig index 8cb7b943e3..bd7b3af733 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -189,6 +189,10 @@ pub const Inst = struct { /// Converts a pointer to its address. Result type is always `usize`. /// Uses the `un_op` field. ptrtoint, + /// Given a boolean, returns 0 or 1. + /// Result type is always `u1`. + /// Uses the `un_op` field. + bool_to_int, /// Stores a value onto the stack and returns a pointer to it. /// TODO audit where this AIR instruction is emitted, maybe it should instead be emitting /// alloca instruction and storing to the alloca. @@ -490,6 +494,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .slice_len, => return Type.initTag(.usize), + .bool_to_int => return Type.initTag(.u1), + .call => { const callee_ty = air.typeOf(datas[inst].pl_op.operand); return callee_ty.fnReturnType(); diff --git a/src/AstGen.zig b/src/AstGen.zig index 34f906fab1..b5e5d60b2c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7754,6 +7754,7 @@ pub const simple_types = std.ComptimeStringMap(Zir.Inst.Ref, .{ .{ "u32", .u32_type }, .{ "u64", .u64_type }, .{ "u128", .u128_type }, + .{ "u1", .u1_type }, .{ "u8", .u8_type }, .{ "undefined", .undef }, .{ "usize", .usize_type }, @@ -8400,6 +8401,7 @@ fn rvalue( const as_usize = @as(u64, @enumToInt(Zir.Inst.Ref.usize_type)) << 32; const as_void = @as(u64, @enumToInt(Zir.Inst.Ref.void_type)) << 32; switch ((@as(u64, @enumToInt(ty_inst)) << 32) | @as(u64, @enumToInt(result))) { + as_ty | @enumToInt(Zir.Inst.Ref.u1_type), as_ty | @enumToInt(Zir.Inst.Ref.u8_type), as_ty | @enumToInt(Zir.Inst.Ref.i8_type), as_ty | @enumToInt(Zir.Inst.Ref.u16_type), diff --git a/src/Liveness.zig b/src/Liveness.zig index a4c6d8c016..a44b582424 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -291,6 +291,7 @@ fn analyzeInst( .is_err_ptr, .is_non_err_ptr, .ptrtoint, + .bool_to_int, .ret, => { const operand = inst_datas[inst].un_op; diff --git a/src/Sema.zig b/src/Sema.zig index 46a41426c8..88a83fd661 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5848,8 +5848,14 @@ fn zirAlignOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr fn zirBoolToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirBoolToInt", .{}); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand = sema.resolveInst(inst_data.operand); + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(Type.initTag(.u1)); + const bool_ints = [2]Air.Inst.Ref{ .zero, .one }; + return bool_ints[@boolToInt(val.toBool())]; + } + return block.addUnOp(.bool_to_int, operand); } fn zirEmbedFile(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8252,6 +8258,7 @@ fn typeHasOnePossibleValue( .c_longdouble, .comptime_int, .comptime_float, + .u1, .u8, .i8, .u16, diff --git a/src/Zir.zig b/src/Zir.zig index 2b88a3415d..a8320b65d4 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -1633,6 +1633,7 @@ pub const Inst = struct { /// value and may instead be used as a sentinel to indicate null. none, + u1_type, u8_type, i8_type, u16_type, @@ -1719,6 +1720,10 @@ pub const Inst = struct { pub const typed_value_map = std.enums.directEnumArray(Ref, TypedValue, 0, .{ .none = undefined, + .u1_type = .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.u1_type), + }, .u8_type = .{ .ty = Type.initTag(.type), .val = Value.initTag(.u8_type), diff --git a/src/codegen.zig b/src/codegen.zig index 4924b68ca3..3b822c0f88 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -835,6 +835,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .dbg_stmt => try self.airDbgStmt(inst), .floatcast => try self.airFloatCast(inst), .intcast => try self.airIntCast(inst), + .bool_to_int => try self.airBoolToInt(inst), .is_non_null => try self.airIsNonNull(inst), .is_non_null_ptr => try self.airIsNonNullPtr(inst), .is_null => try self.airIsNull(inst), @@ -1110,6 +1111,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } + fn airBoolToInt(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else operand; + return self.finishAir(inst, result, .{ un_op, .none, .none }); + } + fn airNot(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index fa254af293..7299b21a61 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -925,6 +925,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .call => try airCall(o, inst), .dbg_stmt => try airDbgStmt(o, inst), .intcast => try airIntCast(o, inst), + .bool_to_int => try airBoolToInt(o, inst), .load => try airLoad(o, inst), .ret => try airRet(o, inst), .store => try airStore(o, inst), @@ -1083,6 +1084,20 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { return local; } +fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue { + if (o.liveness.isUnused(inst)) + return CValue.none; + const un_op = o.air.instructions.items(.data)[inst].un_op; + const writer = o.writer(); + const inst_ty = o.air.typeOfIndex(inst); + const operand = try o.resolveInst(un_op); + const local = try o.allocLocal(inst_ty, .Const); + try writer.writeAll(" = "); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); + return local; +} + fn airStore(o: *Object, inst: Air.Inst.Index) !CValue { // *a = b; const bin_op = o.air.instructions.items(.data)[inst].bin_op; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 74a51e6634..22f117aa1c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -961,6 +961,7 @@ pub const FuncGen = struct { .alloc => try self.airAlloc(inst), .arg => try self.airArg(inst), .bitcast => try self.airBitCast(inst), + .bool_to_int=> try self.airBoolToInt(inst), .block => try self.airBlock(inst), .br => try self.airBr(inst), .switch_br => try self.airSwitchBr(inst), @@ -1656,6 +1657,15 @@ pub const FuncGen = struct { return self.builder.buildBitCast(operand, dest_type, ""); } + fn airBoolToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + return operand; + } + fn airArg(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const arg_val = self.args[self.arg_index]; self.arg_index += 1; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 0c9e513742..4f5df73f8d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -885,7 +885,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { // Both stage1 and stage2 LLVM backend put the object file in the cache directory. if (self.base.options.use_llvm) { // Stage2 has to call flushModule since that outputs the LLVM object file. - if (!build_options.is_stage1) try self.flushModule(comp); + if (!build_options.is_stage1 or !self.base.options.use_stage1) try self.flushModule(comp); const obj_basename = try std.zig.binNameAlloc(arena, .{ .root_name = self.base.options.root_name, @@ -1269,7 +1269,10 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { // TODO: remove when stage2 can build compiler_rt.zig, c.zig and ssp.zig // compiler-rt, libc and libssp - if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies and build_options.is_stage1) { + if (is_exe_or_dyn_lib and + !self.base.options.skip_linker_dependencies and + build_options.is_stage1 and self.base.options.use_stage1) + { if (!self.base.options.link_libc) { try argv.append(comp.libc_static_lib.?.full_object_path); } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 502575f3c8..9ddebd3453 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1257,7 +1257,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { // Both stage1 and stage2 LLVM backend put the object file in the cache directory. if (self.base.options.use_llvm) { // Stage2 has to call flushModule since that outputs the LLVM object file. - if (!build_options.is_stage1) try self.flushModule(comp); + if (!build_options.is_stage1 or !self.base.options.use_stage1) try self.flushModule(comp); const obj_basename = try std.zig.binNameAlloc(arena, .{ .root_name = self.base.options.root_name, @@ -1287,7 +1287,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { 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 (!build_options.is_stage1 or !self.base.options.use_stage1) break :blk null; // In the case of build-obj we include the compiler-rt symbols directly alongside // the symbols of the root source file, in the same compilation unit. @@ -1605,7 +1605,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies and !self.base.options.link_libc and - build_options.is_stage1) + build_options.is_stage1 and + self.base.options.use_stage1) { try argv.append(comp.libc_static_lib.?.full_object_path); } diff --git a/src/print_air.zig b/src/print_air.zig index c20a6995e5..2d5d6b588e 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -137,6 +137,7 @@ const Writer = struct { .is_err_ptr, .is_non_err_ptr, .ptrtoint, + .bool_to_int, .ret, => try w.writeUnOp(s, inst), diff --git a/src/type.zig b/src/type.zig index 4dd1a15fdd..d828df550b 100644 --- a/src/type.zig +++ b/src/type.zig @@ -23,6 +23,7 @@ pub const Type = extern union { pub fn zigTypeTag(self: Type) std.builtin.TypeId { switch (self.tag()) { + .u1, .u8, .i8, .u16, @@ -638,6 +639,7 @@ pub const Type = extern union { if (self.tag_if_small_enough < Tag.no_payload_count) { return Type{ .tag_if_small_enough = self.tag_if_small_enough }; } else switch (self.ptr_otherwise.tag) { + .u1, .u8, .i8, .u16, @@ -819,6 +821,7 @@ pub const Type = extern union { while (true) { const t = ty.tag(); switch (t) { + .u1, .u8, .i8, .u16, @@ -1082,6 +1085,7 @@ pub const Type = extern union { pub fn toValue(self: Type, allocator: *Allocator) Allocator.Error!Value { switch (self.tag()) { + .u1 => return Value.initTag(.u1_type), .u8 => return Value.initTag(.u8_type), .i8 => return Value.initTag(.i8_type), .u16 => return Value.initTag(.u16_type), @@ -1141,6 +1145,7 @@ pub const Type = extern union { pub fn hasCodeGenBits(self: Type) bool { return switch (self.tag()) { + .u1, .u8, .i8, .u16, @@ -1321,6 +1326,7 @@ pub const Type = extern union { /// Asserts that hasCodeGenBits() is true. pub fn abiAlignment(self: Type, target: Target) u32 { return switch (self.tag()) { + .u1, .u8, .i8, .bool, @@ -1539,6 +1545,7 @@ pub const Type = extern union { @panic("TODO abiSize unions"); }, + .u1, .u8, .i8, .bool, @@ -1704,7 +1711,7 @@ pub const Type = extern union { .u8, .i8 => 8, - .bool => 1, + .bool, .u1 => 1, .vector => { const payload = self.castTag(.vector).?.data; @@ -2217,12 +2224,13 @@ pub const Type = extern union { pub fn isUnsignedInt(self: Type) bool { return switch (self.tag()) { .int_unsigned, - .u8, .usize, .c_ushort, .c_uint, .c_ulong, .c_ulonglong, + .u1, + .u8, .u16, .u32, .u64, @@ -2244,6 +2252,7 @@ pub const Type = extern union { .signedness = .signed, .bits = self.castTag(.int_signed).?.data, }, + .u1 => .{ .signedness = .unsigned, .bits = 1 }, .u8 => .{ .signedness = .unsigned, .bits = 8 }, .i8 => .{ .signedness = .signed, .bits = 8 }, .u16 => .{ .signedness = .unsigned, .bits = 16 }, @@ -2406,6 +2415,7 @@ pub const Type = extern union { .c_longdouble, .comptime_int, .comptime_float, + .u1, .u8, .i8, .u16, @@ -2446,6 +2456,7 @@ pub const Type = extern union { .c_longdouble, .comptime_int, .comptime_float, + .u1, .u8, .i8, .u16, @@ -2911,6 +2922,7 @@ pub const Type = extern union { /// See `zigTypeTag` for the function that corresponds to `std.builtin.TypeId`. pub const Tag = enum { // The first section of this enum are tags that require no payload. + u1, u8, i8, u16, @@ -3018,6 +3030,7 @@ pub const Type = extern union { pub fn Type(comptime t: Tag) type { return switch (t) { + .u1, .u8, .i8, .u16, diff --git a/src/value.zig b/src/value.zig index fc34473921..5d9fd27414 100644 --- a/src/value.zig +++ b/src/value.zig @@ -22,6 +22,7 @@ pub const Value = extern union { pub const Tag = enum { // The first section of this enum are tags that require no payload. + u1_type, u8_type, i8_type, u16_type, @@ -138,6 +139,7 @@ pub const Value = extern union { pub fn Type(comptime t: Tag) type { return switch (t) { + .u1_type, .u8_type, .i8_type, .u16_type, @@ -314,6 +316,7 @@ pub const Value = extern union { if (self.tag_if_small_enough < Tag.no_payload_count) { return Value{ .tag_if_small_enough = self.tag_if_small_enough }; } else switch (self.ptr_otherwise.tag) { + .u1_type, .u8_type, .i8_type, .u16_type, @@ -520,6 +523,7 @@ pub const Value = extern union { comptime assert(fmt.len == 0); var val = start_val; while (true) switch (val.tag()) { + .u1_type => return out_stream.writeAll("u1"), .u8_type => return out_stream.writeAll("u8"), .i8_type => return out_stream.writeAll("i8"), .u16_type => return out_stream.writeAll("u16"), @@ -671,6 +675,7 @@ pub const Value = extern union { pub fn toType(self: Value, allocator: *Allocator) !Type { return switch (self.tag()) { .ty => self.castTag(.ty).?.data, + .u1_type => Type.initTag(.u1), .u8_type => Type.initTag(.u8), .i8_type => Type.initTag(.i8), .u16_type => Type.initTag(.u16), @@ -1150,6 +1155,7 @@ pub const Value = extern union { var hasher = std.hash.Wyhash.init(0); switch (self.tag()) { + .u1_type, .u8_type, .i8_type, .u16_type, @@ -1502,6 +1508,7 @@ pub const Value = extern union { return switch (self.tag()) { .ty, .int_type, + .u1_type, .u8_type, .i8_type, .u16_type, diff --git a/test/behavior.zig b/test/behavior.zig index 101ee2ce53..a286b1a6e2 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -2,11 +2,9 @@ const builtin = @import("builtin"); test { // Tests that pass for both. - {} + _ = @import("behavior/bool.zig"); - if (builtin.zig_is_stage2) { - // Tests that only pass for stage2. - } else { + if (!builtin.zig_is_stage2) { // Tests that only pass for stage1. _ = @import("behavior/align.zig"); _ = @import("behavior/alignof.zig"); @@ -20,7 +18,6 @@ test { _ = @import("behavior/bit_shifting.zig"); _ = @import("behavior/bitcast.zig"); _ = @import("behavior/bitreverse.zig"); - _ = @import("behavior/bool.zig"); _ = @import("behavior/bugs/1025.zig"); _ = @import("behavior/bugs/1076.zig"); _ = @import("behavior/bugs/1111.zig"); -- cgit v1.2.3 From a5c6e51f03ab164e64b1a1d8370071dd1e670458 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jul 2021 15:59:51 -0700 Subject: stage2: more principled approach to comptime references * AIR no longer has a `variables` array. Instead of the `varptr` instruction, Sema emits a constant with a `decl_ref`. * AIR no longer has a `ref` instruction. There is no longer any instruction that takes a value and returns a pointer to it. If this is desired, Sema must either create an anynomous Decl and return a constant `decl_ref`, or in the case of a runtime value, emit an `alloc` instruction, `store` the value to it, and then return the `alloc`. * The `ref_val` Value Tag is eliminated. `decl_ref` should be used instead. Also added is `eu_payload_ptr` which points to the payload of an error union, given an error union pointer. In general, Sema should avoid calling `analyzeRef` if it can be helped. For example in the case of field_val and elem_val, there should never be a reason to create a temporary (alloc or decl). Recent previous commits made progress along that front. There is a new abstraction in Sema, which looks like this: var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); // here 'anon_decl.arena()` may be used const decl = try anon_decl.finish(ty, val); // decl is typically now used with `decl_ref`. This pattern is used to upgrade `ref_val` usages to `decl_ref` usages. Additional improvements: * Sema: fix source location resolution for calling convention expression. * Sema: properly report "unable to resolve comptime value" for loads of global variables. There is now a set of functions which can be called if the callee wants to obtain the Value even if the tag is `variable` (indicating comptime-known address but runtime-known value). * Sema: `coerce` resolves builtin types before checking equality. * Sema: fix `u1_type` missing from `addType`, making this type have a slightly more efficient representation in AIR. * LLVM backend: fix `genTypedValue` for tags `decl_ref` and `variable` to properly do an LLVMConstBitCast. * Remove unused parameter from `Value.toEnum`. After this commit, some test cases are no longer passing. This is due to the more principled approach to comptime references causing more anonymous decls to get sent to the linker for codegen. However, in all these cases the decls are not actually referenced by the runtime machine code. A future commit in this branch will implement garbage collection of decls so that unused decls do not get sent to the linker for codegen. This will make the tests go back to passing. --- src/Air.zig | 14 ---- src/Liveness.zig | 2 - src/Module.zig | 47 ++++++++++- src/Sema.zig | 191 ++++++++++++++++++++++++++++-------------- src/codegen.zig | 41 --------- src/codegen/c.zig | 41 +-------- src/codegen/llvm.zig | 35 +------- src/codegen/llvm/bindings.zig | 3 + src/codegen/wasm.zig | 59 ++++++------- src/link/Wasm.zig | 2 +- src/print_air.zig | 13 +-- src/value.zig | 71 +++++++--------- test/cases.zig | 5 +- 13 files changed, 246 insertions(+), 278 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Air.zig b/src/Air.zig index bd7b3af733..fb95d60d00 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -15,7 +15,6 @@ instructions: std.MultiArrayList(Inst).Slice, /// The first few indexes are reserved. See `ExtraIndex` for the values. extra: []const u32, values: []const Value, -variables: []const *Module.Var, pub const ExtraIndex = enum(u32) { /// Payload index of the main `Block` in the `extra` array. @@ -193,20 +192,10 @@ pub const Inst = struct { /// Result type is always `u1`. /// Uses the `un_op` field. bool_to_int, - /// Stores a value onto the stack and returns a pointer to it. - /// TODO audit where this AIR instruction is emitted, maybe it should instead be emitting - /// alloca instruction and storing to the alloca. - /// Uses the `ty_op` field. - ref, /// Return a value from a function. /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `un_op` field. ret, - /// Returns a pointer to a global variable. - /// Uses the `ty_pl` field. Index is into the `variables` array. - /// TODO this can be modeled simply as a constant with a decl ref and then - /// the variables array can be removed from Air. - varptr, /// Write a value to a pointer. LHS is pointer, RHS is value. /// Result type is always void. /// Uses the `bin_op` field. @@ -454,7 +443,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .assembly, .block, .constant, - .varptr, .struct_field_ptr, .struct_field_val, => return air.getRefType(datas[inst].ty_pl.ty), @@ -462,7 +450,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .not, .bitcast, .load, - .ref, .floatcast, .intcast, .optional_payload, @@ -550,7 +537,6 @@ pub fn deinit(air: *Air, gpa: *std.mem.Allocator) void { air.instructions.deinit(gpa); gpa.free(air.extra); gpa.free(air.values); - gpa.free(air.variables); air.* = undefined; } diff --git a/src/Liveness.zig b/src/Liveness.zig index a44b582424..7ba062fa31 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -256,14 +256,12 @@ fn analyzeInst( .const_ty, .breakpoint, .dbg_stmt, - .varptr, .unreach, => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }), .not, .bitcast, .load, - .ref, .floatcast, .intcast, .optional_payload, diff --git a/src/Module.zig b/src/Module.zig index 48c2eb8d0d..f89f72bc65 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1324,6 +1324,42 @@ pub const Scope = struct { block.instructions.appendAssumeCapacity(result_index); return result_index; } + + pub fn startAnonDecl(block: *Block) !WipAnonDecl { + return WipAnonDecl{ + .block = block, + .new_decl_arena = std.heap.ArenaAllocator.init(block.sema.gpa), + .finished = false, + }; + } + + pub const WipAnonDecl = struct { + block: *Scope.Block, + new_decl_arena: std.heap.ArenaAllocator, + finished: bool, + + pub fn arena(wad: *WipAnonDecl) *Allocator { + return &wad.new_decl_arena.allocator; + } + + pub fn deinit(wad: *WipAnonDecl) void { + if (!wad.finished) { + wad.new_decl_arena.deinit(); + } + wad.* = undefined; + } + + pub fn finish(wad: *WipAnonDecl, ty: Type, val: Value) !*Decl { + const new_decl = try wad.block.sema.mod.createAnonymousDecl(&wad.block.base, .{ + .ty = ty, + .val = val, + }); + errdefer wad.block.sema.mod.deleteAnonDecl(&wad.block.base, new_decl); + try new_decl.finalizeNewArena(&wad.new_decl_arena); + wad.finished = true; + return new_decl; + } + }; }; }; @@ -1700,6 +1736,7 @@ pub const SrcLoc = struct { .node_offset_fn_type_cc => |node_off| { const tree = try src_loc.file_scope.getTree(gpa); + const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = src_loc.declRelativeToNodeIndex(node_off); var params: [1]ast.Node.Index = undefined; @@ -1708,6 +1745,13 @@ pub const SrcLoc = struct { .fn_proto_multi => tree.fnProtoMulti(node), .fn_proto_one => tree.fnProtoOne(¶ms, node), .fn_proto => tree.fnProto(node), + .fn_decl => switch (node_tags[node_datas[node].lhs]) { + .fn_proto_simple => tree.fnProtoSimple(¶ms, node_datas[node].lhs), + .fn_proto_multi => tree.fnProtoMulti(node_datas[node].lhs), + .fn_proto_one => tree.fnProtoOne(¶ms, node_datas[node].lhs), + .fn_proto => tree.fnProto(node_datas[node].lhs), + else => unreachable, + }, else => unreachable, }; const main_tokens = tree.nodes.items(.main_token); @@ -2935,7 +2979,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { const break_index = try sema.analyzeBody(&block_scope, body); const result_ref = zir_datas[break_index].@"break".operand; const src: LazySrcLoc = .{ .node_offset = 0 }; - const decl_tv = try sema.resolveInstConst(&block_scope, src, result_ref); + const decl_tv = try sema.resolveInstValue(&block_scope, src, result_ref); const align_val = blk: { const align_ref = decl.zirAlignRef(); if (align_ref == .none) break :blk Value.initTag(.null_value); @@ -3603,7 +3647,6 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { .instructions = sema.air_instructions.toOwnedSlice(), .extra = sema.air_extra.toOwnedSlice(gpa), .values = sema.air_values.toOwnedSlice(gpa), - .variables = sema.air_variables.toOwnedSlice(gpa), }; } diff --git a/src/Sema.zig b/src/Sema.zig index 88a83fd661..95baae7e92 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14,7 +14,6 @@ code: Zir, air_instructions: std.MultiArrayList(Air.Inst) = .{}, air_extra: std.ArrayListUnmanaged(u32) = .{}, air_values: std.ArrayListUnmanaged(Value) = .{}, -air_variables: std.ArrayListUnmanaged(*Module.Var) = .{}, /// Maps ZIR to AIR. inst_map: InstMap = .{}, /// When analyzing an inline function call, owner_decl is the Decl of the caller @@ -76,7 +75,6 @@ pub fn deinit(sema: *Sema) void { sema.air_instructions.deinit(gpa); sema.air_extra.deinit(gpa); sema.air_values.deinit(gpa); - sema.air_variables.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); sema.* = undefined; @@ -639,16 +637,40 @@ fn analyzeAsType( return val.toType(sema.arena); } +/// May return Value Tags: `variable`, `undef`. +/// See `resolveConstValue` for an alternative. +fn resolveValue( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + air_ref: Air.Inst.Ref, +) CompileError!Value { + if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| { + return val; + } + return sema.failWithNeededComptime(block, src); +} + +/// Will not return Value Tags: `variable`, `undef`. Instead they will emit compile errors. +/// See `resolveValue` for an alternative. fn resolveConstValue( sema: *Sema, block: *Scope.Block, src: LazySrcLoc, air_ref: Air.Inst.Ref, ) CompileError!Value { - return (try sema.resolveDefinedValue(block, src, air_ref)) orelse - return sema.failWithNeededComptime(block, src); + if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| { + switch (val.tag()) { + .undef => return sema.failWithUseOfUndef(block, src), + .variable => return sema.failWithNeededComptime(block, src), + else => return val, + } + } + return sema.failWithNeededComptime(block, src); } +/// Value Tag `variable` causes this function to return `null`. +/// Value Tag `undef` causes this function to return a compile error. fn resolveDefinedValue( sema: *Sema, block: *Scope.Block, @@ -664,11 +686,27 @@ fn resolveDefinedValue( return null; } +/// Value Tag `variable` causes this function to return `null`. +/// Value Tag `undef` causes this function to return the Value. fn resolveMaybeUndefVal( sema: *Sema, block: *Scope.Block, src: LazySrcLoc, inst: Air.Inst.Ref, +) CompileError!?Value { + const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; + if (val.tag() == .variable) { + return sema.failWithNeededComptime(block, src); + } + return val; +} + +/// Returns all Value tags including `variable` and `undef`. +fn resolveMaybeUndefValAllowVariables( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + inst: Air.Inst.Ref, ) CompileError!?Value { // First section of indexes correspond to a set number of constant values. var i: usize = @enumToInt(inst); @@ -734,6 +772,8 @@ fn resolveInt( return val.toUnsignedInt(); } +// Returns a compile error if the value has tag `variable`. See `resolveInstValue` for +// a function that does not. pub fn resolveInstConst( sema: *Sema, block: *Scope.Block, @@ -748,6 +788,22 @@ pub fn resolveInstConst( }; } +// Value Tag may be `undef` or `variable`. +// See `resolveInstConst` for an alternative. +pub fn resolveInstValue( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + zir_ref: Zir.Inst.Ref, +) CompileError!TypedValue { + const air_ref = sema.resolveInst(zir_ref); + const val = try sema.resolveValue(block, src, air_ref); + return TypedValue{ + .ty = sema.typeOf(air_ref), + .val = val, + }; +} + fn zirBitcastResultPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); @@ -1707,7 +1763,7 @@ fn zirStr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A }); errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); try new_decl.finalizeNewArena(&new_decl_arena); - return sema.analyzeDeclRef(block, .unneeded, new_decl); + return sema.analyzeDeclRef(new_decl); } fn zirInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -2090,10 +2146,7 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro const linkage_index = struct_obj.fields.getIndex("linkage").?; const section_index = struct_obj.fields.getIndex("section").?; const export_name = try fields[name_index].toAllocatedBytes(sema.arena); - const linkage = fields[linkage_index].toEnum( - struct_obj.fields.values()[linkage_index].ty, - std.builtin.GlobalLinkage, - ); + const linkage = fields[linkage_index].toEnum(std.builtin.GlobalLinkage); if (linkage != .Strong) { return sema.mod.fail(&block.base, src, "TODO: implement exporting with non-strong linkage", .{}); @@ -2194,7 +2247,7 @@ fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const src = inst_data.src(); const decl_name = inst_data.get(sema.code); const decl = try sema.lookupIdentifier(block, src, decl_name); - return sema.analyzeDeclRef(block, src, decl); + return sema.analyzeDeclRef(decl); } fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -2978,14 +3031,9 @@ fn zirErrUnionPayloadPtr( if (val.getError()) |name| { return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); } - const data = val.castTag(.error_union).?.data; - // The same Value represents the pointer to the error union and the payload. return sema.addConstant( operand_pointer_ty, - try Value.Tag.ref_val.create( - sema.arena, - data, - ), + try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), ); } @@ -6296,7 +6344,7 @@ fn zirFuncExtended( const cc_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; const cc_tv = try sema.resolveInstConst(block, cc_src, cc_ref); - break :blk cc_tv.val.toEnum(cc_tv.ty, std.builtin.CallingConvention); + break :blk cc_tv.val.toEnum(std.builtin.CallingConvention); } else .Unspecified; const align_val: Value = if (small.has_align) blk: { @@ -6554,7 +6602,7 @@ fn safetyPanic( }); errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); try new_decl.finalizeNewArena(&new_decl_arena); - break :msg_inst try sema.analyzeDeclRef(block, .unneeded, new_decl); + break :msg_inst try sema.analyzeDeclRef(new_decl); }; const casted_msg_inst = try sema.coerce(block, Type.initTag(.const_slice_u8), msg_inst, src); @@ -6761,11 +6809,16 @@ fn fieldPtr( switch (object_ty.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.ref_val.create( + try Value.Tag.decl_ref.create( arena, - try Value.Tag.int_u64.create(arena, object_ty.arrayLen()), + try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), + ), ), ); } else { @@ -6780,18 +6833,25 @@ fn fieldPtr( .Pointer => { const ptr_child = object_ty.elemType(); if (ptr_child.isSlice()) { + // Here for the ptr and len fields what we need to do is the situation + // when a temporary has its address taken, e.g. `&a[c..d].len`. + // This value may be known at compile-time or runtime. In the former + // case, it should create an anonymous Decl and return a decl_ref to it. + // In the latter case, it should add an `alloc` instruction, store + // the runtime value to it, and then return the `alloc`. + // In both cases the pointer should be const. if (mem.eql(u8, field_name, "ptr")) { return mod.fail( &block.base, field_name_src, - "cannot obtain reference to pointer field of slice '{}'", + "TODO: implement reference to 'ptr' field of slice '{}'", .{object_ty}, ); } else if (mem.eql(u8, field_name, "len")) { return mod.fail( &block.base, field_name_src, - "cannot obtain reference to length field of slice '{}'", + "TODO: implement reference to 'len' field of slice '{}'", .{object_ty}, ); } else { @@ -6805,11 +6865,16 @@ fn fieldPtr( } else switch (ptr_child.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.ref_val.create( + try Value.Tag.decl_ref.create( arena, - try Value.Tag.int_u64.create(arena, ptr_child.arrayLen()), + try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), + ), ), ); } else { @@ -6848,13 +6913,16 @@ fn fieldPtr( }); } else (try mod.getErrorValue(field_name)).key; + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.ref_val.create( + try Value.Tag.decl_ref.create( arena, - try Value.Tag.@"error".create(arena, .{ - .name = name, - }), + try anon_decl.finish( + child_type, + try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), + ), ), ); }, @@ -6901,10 +6969,17 @@ fn fieldPtr( return mod.failWithOwnedErrorMsg(&block.base, msg); }; const field_index_u32 = @intCast(u32, field_index); - const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32); + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.ref_val.create(arena, enum_val), + try Value.Tag.decl_ref.create( + arena, + try anon_decl.finish( + child_type, + try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), + ), + ), ); }, else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), @@ -6951,7 +7026,7 @@ fn namespaceLookupRef( decl_name: []const u8, ) CompileError!?Air.Inst.Ref { const decl = (try sema.namespaceLookup(block, src, namespace, decl_name)) orelse return null; - return try sema.analyzeDeclRef(block, src, decl); + return try sema.analyzeDeclRef(decl); } fn structFieldPtr( @@ -7207,13 +7282,15 @@ fn elemPtrArray( fn coerce( sema: *Sema, block: *Scope.Block, - dest_type: Type, + dest_type_unresolved: Type, inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - if (dest_type.tag() == .var_args_param) { + if (dest_type_unresolved.tag() == .var_args_param) { return sema.coerceVarArgParam(block, inst, inst_src); } + const dest_type_src = inst_src; // TODO better source location + const dest_type = try sema.resolveTypeFields(block, dest_type_src, dest_type_unresolved); const inst_ty = sema.typeOf(inst); // If the types are the same, we can return the operand. @@ -7554,17 +7631,17 @@ fn analyzeDeclVal( if (sema.decl_val_table.get(decl)) |result| { return result; } - const decl_ref = try sema.analyzeDeclRef(block, src, decl); + const decl_ref = try sema.analyzeDeclRef(decl); const result = try sema.analyzeLoad(block, src, decl_ref, src); if (Air.refToIndex(result)) |index| { if (sema.air_instructions.items(.tag)[index] == .constant) { - sema.decl_val_table.put(sema.gpa, decl, result) catch {}; + try sema.decl_val_table.put(sema.gpa, decl, result); } } return result; } -fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl) CompileError!Air.Inst.Ref { +fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { try sema.mod.declareDeclDependency(sema.owner_decl, decl); sema.mod.ensureDeclAnalyzed(decl) catch |err| { if (sema.func) |func| { @@ -7576,8 +7653,10 @@ fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl }; const decl_tv = try decl.typedValue(); - if (decl_tv.val.tag() == .variable) { - return sema.analyzeVarRef(block, src, decl_tv); + if (decl_tv.val.castTag(.variable)) |payload| { + const variable = payload.data; + const ty = try Module.simplePtrType(sema.arena, decl_tv.ty, variable.is_mutable, .One); + return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl)); } return sema.addConstant( try Module.simplePtrType(sema.arena, decl_tv.ty, false, .One), @@ -7585,26 +7664,6 @@ fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl ); } -fn analyzeVarRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, tv: TypedValue) CompileError!Air.Inst.Ref { - const variable = tv.val.castTag(.variable).?.data; - - const ty = try Module.simplePtrType(sema.arena, tv.ty, variable.is_mutable, .One); - if (!variable.is_mutable and !variable.is_extern) { - return sema.addConstant(ty, try Value.Tag.ref_val.create(sema.arena, variable.init)); - } - - const gpa = sema.gpa; - try sema.requireRuntimeBlock(block, src); - try sema.air_variables.append(gpa, variable); - return block.addInst(.{ - .tag = .varptr, - .data = .{ .ty_pl = .{ - .ty = try sema.addType(ty), - .payload = @intCast(u32, sema.air_variables.items.len - 1), - } }, - }); -} - fn analyzeRef( sema: *Sema, block: *Scope.Block, @@ -7615,11 +7674,21 @@ fn analyzeRef( const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| { - return sema.addConstant(ptr_type, try Value.Tag.ref_val.create(sema.arena, val)); + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + return sema.addConstant( + ptr_type, + try Value.Tag.decl_ref.create( + sema.arena, + try anon_decl.finish(operand_ty, try val.copy(anon_decl.arena())), + ), + ); } try sema.requireRuntimeBlock(block, src); - return block.addTyOp(.ref, ptr_type, operand); + const alloc = try block.addTy(.alloc, ptr_type); + try sema.storePtr(block, src, alloc, operand); + return alloc; } fn analyzeLoad( @@ -8447,12 +8516,12 @@ fn getTmpAir(sema: Sema) Air { .instructions = sema.air_instructions.slice(), .extra = sema.air_extra.items, .values = sema.air_values.items, - .variables = sema.air_variables.items, }; } pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref { switch (ty.tag()) { + .u1 => return .u1_type, .u8 => return .u8_type, .i8 => return .i8_type, .u16 => return .u16_type, diff --git a/src/codegen.zig b/src/codegen.zig index 3b822c0f88..b7ba367d54 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -848,13 +848,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .loop => try self.airLoop(inst), .not => try self.airNot(inst), .ptrtoint => try self.airPtrToInt(inst), - .ref => try self.airRef(inst), .ret => try self.airRet(inst), .store => try self.airStore(inst), .struct_field_ptr=> try self.airStructFieldPtr(inst), .struct_field_val=> try self.airStructFieldVal(inst), .switch_br => try self.airSwitch(inst), - .varptr => try self.airVarPtr(inst), .slice_ptr => try self.airSlicePtr(inst), .slice_len => try self.airSliceLen(inst), @@ -1340,13 +1338,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } - fn airVarPtr(self: *Self, inst: Air.Inst.Index) !void { - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { - else => return self.fail("TODO implement varptr for {}", .{self.target.cpu.arch}), - }; - return self.finishAir(inst, result, .{ .none, .none, .none }); - } - fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { @@ -2833,38 +2824,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return bt.finishAir(result); } - fn airRef(self: *Self, inst: Air.Inst.Index) !void { - const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { - const operand_ty = self.air.typeOf(ty_op.operand); - const operand = try self.resolveInst(ty_op.operand); - switch (operand) { - .unreach => unreachable, - .dead => unreachable, - .none => break :result MCValue{ .none = {} }, - - .immediate, - .register, - .ptr_stack_offset, - .ptr_embedded_in_code, - .compare_flags_unsigned, - .compare_flags_signed, - => { - const stack_offset = try self.allocMemPtr(inst); - try self.genSetStack(operand_ty, stack_offset, operand); - break :result MCValue{ .ptr_stack_offset = stack_offset }; - }, - - .stack_offset => |offset| break :result MCValue{ .ptr_stack_offset = offset }, - .embedded_in_code => |offset| break :result MCValue{ .ptr_embedded_in_code = offset }, - .memory => |vaddr| break :result MCValue{ .immediate = vaddr }, - - .undef => return self.fail("TODO implement ref on an undefined value", .{}), - } - }; - return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); - } - fn ret(self: *Self, mcv: MCValue) !void { const ret_ty = self.fn_type.fnReturnType(); try self.setRegOrMem(ret_ty, self.ret_mcv, mcv); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 7299b21a61..a8ec677753 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -283,22 +283,7 @@ pub const DeclGen = struct { }, else => switch (t.ptrSize()) { .Slice => unreachable, - .Many => { - if (val.castTag(.ref_val)) |ref_val_payload| { - const sub_val = ref_val_payload.data; - if (sub_val.castTag(.bytes)) |bytes_payload| { - const bytes = bytes_payload.data; - try writer.writeByte('('); - try dg.renderType(writer, t); - // TODO: make our own C string escape instead of using std.zig.fmtEscapes - try writer.print(")\"{}\"", .{std.zig.fmtEscapes(bytes)}); - } else { - unreachable; - } - } else { - unreachable; - } - }, + .Many => unreachable, .One => { var arena = std.heap.ArenaAllocator.init(dg.module.gpa); defer arena.deinit(); @@ -934,10 +919,8 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .br => try airBr(o, inst), .switch_br => try airSwitchBr(o, inst), .wrap_optional => try airWrapOptional(o, inst), - .ref => try airRef(o, inst), .struct_field_ptr => try airStructFieldPtr(o, inst), .struct_field_val => try airStructFieldVal(o, inst), - .varptr => try airVarPtr(o, inst), .slice_ptr => try airSliceField(o, inst, ".ptr;\n"), .slice_len => try airSliceField(o, inst, ".len;\n"), @@ -996,12 +979,6 @@ fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue return local; } -fn airVarPtr(o: *Object, inst: Air.Inst.Index) !CValue { - const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; - const variable = o.air.variables[ty_pl.payload]; - return CValue{ .decl_ref = variable.owner_decl }; -} - fn airAlloc(o: *Object, inst: Air.Inst.Index) !CValue { const writer = o.writer(); const inst_ty = o.air.typeOfIndex(inst); @@ -1653,22 +1630,6 @@ fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue { return local; } -fn airRef(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) - return CValue.none; - - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); - - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); - try writer.writeAll(" = "); - try o.writeCValue(writer, operand); - try writer.writeAll(";\n"); - return local; -} - fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue { if (o.liveness.isUnused(inst)) return CValue.none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 22f117aa1c..0e9a572bea 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -699,29 +699,12 @@ pub const DeclGen = struct { .decl_ref => { const decl = tv.val.castTag(.decl_ref).?.data; const val = try self.resolveGlobalDecl(decl); - - const usize_type = try self.llvmType(Type.initTag(.usize)); - - // TODO: second index should be the index into the memory! - var indices: [2]*const llvm.Value = .{ - usize_type.constNull(), - usize_type.constNull(), - }; - - return val.constInBoundsGEP(&indices, indices.len); - }, - .ref_val => { - //const elem_value = tv.val.castTag(.ref_val).?.data; - //const elem_type = tv.ty.castPointer().?.data; - //const alloca = fg.?.buildAlloca(try self.llvmType(elem_type)); - //_ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca); - //return alloca; - // TODO eliminate the ref_val Value Tag - return self.todo("implement const of pointer tag ref_val", .{}); + return val.constBitCast(llvm_type); }, .variable => { const variable = tv.val.castTag(.variable).?.data; - return self.resolveGlobalDecl(variable.owner_decl); + const val = try self.resolveGlobalDecl(variable.owner_decl); + return val.constBitCast(llvm_type); }, .slice => { const slice = tv.val.castTag(.slice).?.data; @@ -977,7 +960,6 @@ pub const FuncGen = struct { .ret => try self.airRet(inst), .store => try self.airStore(inst), .assembly => try self.airAssembly(inst), - .varptr => try self.airVarPtr(inst), .slice_ptr => try self.airSliceField(inst, 0), .slice_len => try self.airSliceField(inst, 1), @@ -1001,7 +983,6 @@ pub const FuncGen = struct { .constant => unreachable, .const_ty => unreachable, - .ref => unreachable, // TODO eradicate this instruction .unreach => self.airUnreach(inst), .dbg_stmt => blk: { // TODO: implement debug info @@ -1180,16 +1161,6 @@ pub const FuncGen = struct { return null; } - fn airVarPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; - - const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; - const variable = self.air.variables[ty_pl.payload]; - const decl_llvm_value = self.dg.resolveGlobalDecl(variable.owner_decl); - return decl_llvm_value; - } - fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 6d09d69447..0977d6128d 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -112,6 +112,9 @@ pub const Value = opaque { ConstantIndices: [*]const *const Value, NumIndices: c_uint, ) *const Value; + + pub const constBitCast = LLVMConstBitCast; + extern fn LLVMConstBitCast(ConstantVal: *const Value, ToType: *const Type) *const Value; }; pub const Type = opaque { diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index bf9010fbff..37cc6bc59c 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -754,22 +754,21 @@ pub const Context = struct { } /// Generates the wasm bytecode for the declaration belonging to `Context` - pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result { - switch (typed_value.ty.zigTypeTag()) { + pub fn gen(self: *Context, ty: Type, val: Value) InnerError!Result { + switch (ty.zigTypeTag()) { .Fn => { try self.genFunctype(); - if (typed_value.val.castTag(.extern_fn)) |_| return Result.appended; // don't need code body for extern functions + if (val.tag() == .extern_fn) { + return Result.appended; // don't need code body for extern functions + } return self.fail("TODO implement wasm codegen for function pointers", .{}); }, .Array => { - if (typed_value.val.castTag(.bytes)) |payload| { - if (typed_value.ty.sentinel()) |sentinel| { + if (val.castTag(.bytes)) |payload| { + if (ty.sentinel()) |sentinel| { try self.code.appendSlice(payload.data); - switch (try self.gen(.{ - .ty = typed_value.ty.elemType(), - .val = sentinel, - })) { + switch (try self.gen(ty.elemType(), sentinel)) { .appended => return Result.appended, .externally_managed => |data| { try self.code.appendSlice(data); @@ -781,13 +780,17 @@ pub const Context = struct { } else return self.fail("TODO implement gen for more kinds of arrays", .{}); }, .Int => { - const info = typed_value.ty.intInfo(self.target); + const info = ty.intInfo(self.target); if (info.bits == 8 and info.signedness == .unsigned) { - const int_byte = typed_value.val.toUnsignedInt(); + const int_byte = val.toUnsignedInt(); try self.code.append(@intCast(u8, int_byte)); return Result.appended; } - return self.fail("TODO: Implement codegen for int type: '{}'", .{typed_value.ty}); + return self.fail("TODO: Implement codegen for int type: '{}'", .{ty}); + }, + .Enum => { + try self.emitConstant(val, ty); + return Result.appended; }, else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}), } @@ -969,7 +972,7 @@ pub const Context = struct { return WValue{ .code_offset = offset }; } - fn emitConstant(self: *Context, value: Value, ty: Type) InnerError!void { + fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void { const writer = self.code.writer(); switch (ty.zigTypeTag()) { .Int => { @@ -982,10 +985,10 @@ pub const Context = struct { const int_info = ty.intInfo(self.target); // write constant switch (int_info.signedness) { - .signed => try leb.writeILEB128(writer, value.toSignedInt()), + .signed => try leb.writeILEB128(writer, val.toSignedInt()), .unsigned => switch (int_info.bits) { - 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, value.toUnsignedInt()))), - 33...64 => try leb.writeILEB128(writer, @bitCast(i64, value.toUnsignedInt())), + 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, val.toUnsignedInt()))), + 33...64 => try leb.writeILEB128(writer, @bitCast(i64, val.toUnsignedInt())), else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}), }, } @@ -994,7 +997,7 @@ pub const Context = struct { // write opcode try writer.writeByte(wasm.opcode(.i32_const)); // write constant - try leb.writeILEB128(writer, value.toSignedInt()); + try leb.writeILEB128(writer, val.toSignedInt()); }, .Float => { // write opcode @@ -1005,13 +1008,13 @@ pub const Context = struct { try writer.writeByte(wasm.opcode(opcode)); // write constant switch (ty.floatBits(self.target)) { - 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, value.toFloat(f32))), - 64 => try writer.writeIntLittle(u64, @bitCast(u64, value.toFloat(f64))), + 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, val.toFloat(f32))), + 64 => try writer.writeIntLittle(u64, @bitCast(u64, val.toFloat(f64))), else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}), } }, .Pointer => { - if (value.castTag(.decl_ref)) |payload| { + if (val.castTag(.decl_ref)) |payload| { const decl = payload.data; // offset into the offset table within the 'data' section @@ -1024,11 +1027,11 @@ pub const Context = struct { try writer.writeByte(wasm.opcode(.i32_load)); try leb.writeULEB128(writer, @as(u32, 0)); try leb.writeULEB128(writer, @as(u32, 0)); - } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{value.tag()}); + } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()}); }, .Void => {}, .Enum => { - if (value.castTag(.enum_field_index)) |field_index| { + if (val.castTag(.enum_field_index)) |field_index| { switch (ty.tag()) { .enum_simple => { try writer.writeByte(wasm.opcode(.i32_const)); @@ -1049,20 +1052,20 @@ pub const Context = struct { } else { var int_tag_buffer: Type.Payload.Bits = undefined; const int_tag_ty = ty.intTagType(&int_tag_buffer); - try self.emitConstant(value, int_tag_ty); + try self.emitConstant(val, int_tag_ty); } }, .ErrorSet => { - const error_index = self.global_error_set.get(value.getError().?).?; + const error_index = self.global_error_set.get(val.getError().?).?; try writer.writeByte(wasm.opcode(.i32_const)); try leb.writeULEB128(writer, error_index); }, .ErrorUnion => { - const data = value.castTag(.error_union).?.data; + const data = val.castTag(.error_union).?.data; const error_type = ty.errorUnionSet(); const payload_type = ty.errorUnionPayload(); - if (value.getError()) |_| { - // write the error value + if (val.getError()) |_| { + // write the error val try self.emitConstant(data, error_type); // no payload, so write a '0' const @@ -1085,7 +1088,7 @@ pub const Context = struct { } /// Returns a `Value` as a signed 32 bit value. - /// It's illegale to provide a value with a type that cannot be represented + /// It's illegal to provide a value with a type that cannot be represented /// as an integer value. fn valueAsI32(self: Context, val: Value, ty: Type) i32 { switch (ty.zigTypeTag()) { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 23d0543494..3c3cd4eef3 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -275,7 +275,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { defer context.deinit(); // generate the 'code' section for the function declaration - const result = context.gen(.{ .ty = decl.ty, .val = decl.val }) catch |err| switch (err) { + const result = context.gen(decl.ty, decl.val) catch |err| switch (err) { error.CodegenFail => { decl.analysis = .codegen_failure; try module.failed_decls.put(module.gpa, decl, context.err_msg); diff --git a/src/print_air.zig b/src/print_air.zig index 2d5d6b588e..5d77c303bb 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -15,12 +15,11 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void { (@sizeOf(Air.Inst.Tag) + 8); const extra_bytes = air.extra.len * @sizeOf(u32); const values_bytes = air.values.len * @sizeOf(Value); - const variables_bytes = air.variables.len * @sizeOf(*Module.Var); const tomb_bytes = liveness.tomb_bits.len * @sizeOf(usize); const liveness_extra_bytes = liveness.extra.len * @sizeOf(u32); const liveness_special_bytes = liveness.special.count() * 8; const total_bytes = @sizeOf(Air) + instruction_bytes + extra_bytes + - values_bytes * variables_bytes + @sizeOf(Liveness) + liveness_extra_bytes + + values_bytes + @sizeOf(Liveness) + liveness_extra_bytes + liveness_special_bytes + tomb_bytes; // zig fmt: off @@ -29,7 +28,6 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void { \\# AIR Instructions: {d} ({}) \\# AIR Extra Data: {d} ({}) \\# AIR Values Bytes: {d} ({}) - \\# AIR Variables Bytes: {d} ({}) \\# Liveness tomb_bits: {} \\# Liveness Extra Data: {d} ({}) \\# Liveness special table: {d} ({}) @@ -39,7 +37,6 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void { air.instructions.len, fmtIntSizeBin(instruction_bytes), air.extra.len, fmtIntSizeBin(extra_bytes), air.values.len, fmtIntSizeBin(values_bytes), - air.variables.len, fmtIntSizeBin(variables_bytes), fmtIntSizeBin(tomb_bytes), liveness.extra.len, fmtIntSizeBin(liveness_extra_bytes), liveness.special.count(), fmtIntSizeBin(liveness_special_bytes), @@ -152,7 +149,6 @@ const Writer = struct { .not, .bitcast, .load, - .ref, .floatcast, .intcast, .optional_payload, @@ -174,7 +170,6 @@ const Writer = struct { .struct_field_ptr => try w.writeStructField(s, inst), .struct_field_val => try w.writeStructField(s, inst), - .varptr => try w.writeVarPtr(s, inst), .constant => try w.writeConstant(s, inst), .assembly => try w.writeAssembly(s, inst), .dbg_stmt => try w.writeDbgStmt(s, inst), @@ -243,12 +238,6 @@ const Writer = struct { try s.print(", {d}", .{extra.data.field_index}); } - fn writeVarPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { - _ = w; - _ = inst; - try s.writeAll("TODO"); - } - fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const val = w.air.values[ty_pl.payload]; diff --git a/src/value.zig b/src/value.zig index 5d9fd27414..d3317ef31d 100644 --- a/src/value.zig +++ b/src/value.zig @@ -100,8 +100,6 @@ pub const Value = extern union { function, extern_fn, variable, - /// Represents a pointer to another immutable value. - ref_val, /// Represents a comptime variables storage. comptime_alloc, /// Represents a pointer to a decl, not the value of the decl. @@ -126,6 +124,8 @@ pub const Value = extern union { enum_field_index, @"error", error_union, + /// A pointer to the payload of an error union, based on a pointer to an error union. + eu_payload_ptr, /// An instance of a struct. @"struct", /// An instance of a union. @@ -214,9 +214,9 @@ pub const Value = extern union { .decl_ref, => Payload.Decl, - .ref_val, .repeated, .error_union, + .eu_payload_ptr, => Payload.SubValue, .bytes, @@ -407,15 +407,6 @@ pub const Value = extern union { .function => return self.copyPayloadShallow(allocator, Payload.Function), .extern_fn => return self.copyPayloadShallow(allocator, Payload.Decl), .variable => return self.copyPayloadShallow(allocator, Payload.Variable), - .ref_val => { - const payload = self.castTag(.ref_val).?; - const new_payload = try allocator.create(Payload.SubValue); - new_payload.* = .{ - .base = payload.base, - .data = try payload.data.copy(allocator), - }; - return Value{ .ptr_otherwise = &new_payload.base }; - }, .comptime_alloc => return self.copyPayloadShallow(allocator, Payload.ComptimeAlloc), .decl_ref => return self.copyPayloadShallow(allocator, Payload.Decl), .elem_ptr => { @@ -443,8 +434,8 @@ pub const Value = extern union { return Value{ .ptr_otherwise = &new_payload.base }; }, .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), - .repeated => { - const payload = self.castTag(.repeated).?; + .repeated, .error_union, .eu_payload_ptr => { + const payload = self.cast(Payload.SubValue).?; const new_payload = try allocator.create(Payload.SubValue); new_payload.* = .{ .base = payload.base, @@ -489,15 +480,6 @@ pub const Value = extern union { }, .enum_field_index => return self.copyPayloadShallow(allocator, Payload.U32), .@"error" => return self.copyPayloadShallow(allocator, Payload.Error), - .error_union => { - const payload = self.castTag(.error_union).?; - const new_payload = try allocator.create(Payload.SubValue); - new_payload.* = .{ - .base = payload.base, - .data = try payload.data.copy(allocator), - }; - return Value{ .ptr_otherwise = &new_payload.base }; - }, .@"struct" => @panic("TODO can't copy struct value without knowing the type"), .@"union" => @panic("TODO can't copy union value without knowing the type"), @@ -609,11 +591,6 @@ pub const Value = extern union { .function => return out_stream.print("(function '{s}')", .{val.castTag(.function).?.data.owner_decl.name}), .extern_fn => return out_stream.writeAll("(extern function)"), .variable => return out_stream.writeAll("(variable)"), - .ref_val => { - const ref_val = val.castTag(.ref_val).?.data; - try out_stream.writeAll("&const "); - val = ref_val; - }, .comptime_alloc => { const ref_val = val.castTag(.comptime_alloc).?.data.val; try out_stream.writeAll("&"); @@ -648,6 +625,10 @@ pub const Value = extern union { // TODO to print this it should be error{ Set, Items }!T(val), but we need the type for that .error_union => return out_stream.print("error_union_val({})", .{val.castTag(.error_union).?.data}), .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"), + .eu_payload_ptr => { + try out_stream.writeAll("(eu_payload_ptr)"); + val = val.castTag(.eu_payload_ptr).?.data; + }, }; } @@ -758,7 +739,6 @@ pub const Value = extern union { .function, .extern_fn, .variable, - .ref_val, .comptime_alloc, .decl_ref, .elem_ptr, @@ -780,18 +760,21 @@ pub const Value = extern union { .@"union", .inferred_alloc, .abi_align_default, + .eu_payload_ptr, => unreachable, }; } /// Asserts the type is an enum type. - pub fn toEnum(val: Value, enum_ty: Type, comptime E: type) E { - _ = enum_ty; - // TODO this needs to resolve other kinds of Value tags rather than - // assuming the tag will be .enum_field_index. - const field_index = val.castTag(.enum_field_index).?.data; - // TODO should `@intToEnum` do this `@intCast` for you? - return @intToEnum(E, @intCast(@typeInfo(E).Enum.tag_type, field_index)); + pub fn toEnum(val: Value, comptime E: type) E { + switch (val.tag()) { + .enum_field_index => { + const field_index = val.castTag(.enum_field_index).?.data; + // TODO should `@intToEnum` do this `@intCast` for you? + return @intToEnum(E, @intCast(@typeInfo(E).Enum.tag_type, field_index)); + }, + else => unreachable, + } } /// Asserts the value is an integer. @@ -1255,6 +1238,9 @@ pub const Value = extern union { .slice => { @panic("TODO Value.hash for slice"); }, + .eu_payload_ptr => { + @panic("TODO Value.hash for eu_payload_ptr"); + }, .int_u64 => { const payload = self.castTag(.int_u64).?; std.hash.autoHash(&hasher, payload.data); @@ -1263,10 +1249,6 @@ pub const Value = extern union { const payload = self.castTag(.int_i64).?; std.hash.autoHash(&hasher, payload.data); }, - .ref_val => { - const payload = self.castTag(.ref_val).?; - std.hash.autoHash(&hasher, payload.data.hash()); - }, .comptime_alloc => { const payload = self.castTag(.comptime_alloc).?; std.hash.autoHash(&hasher, payload.data.val.hash()); @@ -1367,7 +1349,6 @@ pub const Value = extern union { pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { return switch (self.tag()) { .comptime_alloc => self.castTag(.comptime_alloc).?.data.val, - .ref_val => self.castTag(.ref_val).?.data, .decl_ref => self.castTag(.decl_ref).?.data.value(), .elem_ptr => { const elem_ptr = self.castTag(.elem_ptr).?.data; @@ -1379,6 +1360,11 @@ pub const Value = extern union { const container_val = try field_ptr.container_ptr.pointerDeref(allocator); return container_val.fieldValue(allocator, field_ptr.field_index); }, + .eu_payload_ptr => { + const err_union_ptr = self.castTag(.eu_payload_ptr).?.data; + const err_union_val = try err_union_ptr.pointerDeref(allocator); + return err_union_val.castTag(.error_union).?.data; + }, else => unreachable, }; @@ -1390,7 +1376,6 @@ pub const Value = extern union { .bytes => val.castTag(.bytes).?.data.len, .array => val.castTag(.array).?.data.len, .slice => val.castTag(.slice).?.data.len.toUnsignedInt(), - .ref_val => sliceLen(val.castTag(.ref_val).?.data), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; if (decl.ty.zigTypeTag() == .Array) { @@ -1576,7 +1561,6 @@ pub const Value = extern union { .int_i64, .int_big_positive, .int_big_negative, - .ref_val, .comptime_alloc, .decl_ref, .elem_ptr, @@ -1599,6 +1583,7 @@ pub const Value = extern union { .@"union", .null_value, .abi_align_default, + .eu_payload_ptr, => false, .undef => unreachable, diff --git a/test/cases.zig b/test/cases.zig index f235992f71..840ee7a4ac 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1182,10 +1182,11 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.obj("extern variable has no type", linux_x64); case.addError( \\comptime { - \\ _ = foo; + \\ const x = foo + foo; + \\ _ = x; \\} \\extern var foo: i32; - , &[_][]const u8{":2:9: error: unable to resolve comptime value"}); + , &[_][]const u8{":2:15: error: unable to resolve comptime value"}); case.addError( \\export fn entry() void { \\ _ = foo; -- cgit v1.2.3 From 040c6eaaa03bbcfcdeadbe835c1c2f209e9f401e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jul 2021 19:30:37 -0700 Subject: stage2: garbage collect unused anon decls After this change, the frontend and backend cooperate to keep track of which Decls are actually emitted into the machine code. When any backend sees a `decl_ref` Value, it must mark the corresponding Decl `alive` field to true. This prevents unused comptime data from spilling into the output object files. For example, if you do an `inline for` loop, previously, any intermediate value calculations would have gone into the object file. Now they are garbage collected immediately after the owner Decl has its machine code generated. In the frontend, when it is time to send a Decl to the linker, if it has not been marked "alive" then it is deleted instead. Additional improvements: * Resolve type ABI layouts after successful semantic analysis of a Decl. This is needed so that the backend has access to struct fields. * Sema: fix incorrect logic in resolveMaybeUndefVal. It should return "not comptime known" instead of a compile error for global variables. * `Value.pointerDeref` now returns `null` in the case that the pointer deref cannot happen at compile-time. This is true for global variables, for example. Another example is if a comptime known pointer has a hard coded address value. * Binary arithmetic sets the requireRuntimeBlock source location to the lhs_src or rhs_src as appropriate instead of on the operator node. * Fix LLVM codegen for slice_elem_val which had the wrong logic for when the operand was not a pointer. As noted in the comment in the implementation of deleteUnusedDecl, a future improvement will be to rework the frontend/linker interface to remove the frontend's responsibility of calling allocateDeclIndexes. I discovered some issues with the plan9 linker backend that are related to this, and worked around them for now. --- src/Compilation.zig | 10 +++- src/Module.zig | 60 +++++++++++++++++--- src/Sema.zig | 154 ++++++++++++++++++++++++++------------------------- src/codegen.zig | 7 +-- src/codegen/c.zig | 24 +++----- src/codegen/llvm.zig | 51 +++++++++++------ src/codegen/wasm.zig | 1 + src/link/Plan9.zig | 19 +++++-- src/value.zig | 45 +++++++++++---- test/stage2/cbe.zig | 2 +- 10 files changed, 234 insertions(+), 139 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 8672a346c3..f8f8cea328 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2061,11 +2061,19 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .complete, .codegen_failure_retryable => { 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.?; assert(decl.has_tv); assert(decl.ty.hasCodeGenBits()); - try module.linkerUpdateDecl(decl); + if (decl.alive) { + try module.linkerUpdateDecl(decl); + continue; + } + + // Instead of sending this decl to the linker, we actually will delete it + // because we found out that it in fact was never referenced. + module.deleteUnusedDecl(decl); }, }, .codegen_func => |func| switch (func.owner_decl.analysis) { diff --git a/src/Module.zig b/src/Module.zig index f89f72bc65..909e54ffa2 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -255,6 +255,15 @@ pub const Decl = struct { has_align: bool, /// Whether the ZIR code provides a linksection instruction. has_linksection: bool, + /// Flag used by garbage collection to mark and sweep. + /// Decls which correspond to an AST node always have this field set to `true`. + /// Anonymous Decls are initialized with this field set to `false` and then it + /// is the responsibility of machine code backends to mark it `true` whenever + /// a `decl_ref` Value is encountered that points to this Decl. + /// When the `codegen_decl` job is encountered in the main work queue, if the + /// Decl is marked alive, then it sends the Decl to the linker. Otherwise it + /// deletes the Decl on the spot. + alive: bool, /// Represents the position of the code in the output file. /// This is populated regardless of semantic analysis and code generation. @@ -2869,6 +2878,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { new_decl.val = struct_val; new_decl.has_tv = true; new_decl.owns_tv = true; + new_decl.alive = true; // This Decl corresponds to a File and is therefore always alive. new_decl.analysis = .in_progress; new_decl.generation = mod.generation; @@ -2990,6 +3000,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { if (linksection_ref == .none) break :blk Value.initTag(.null_value); break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val; }; + try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty); // We need the memory for the Type to go into the arena for the Decl var decl_arena = std.heap.ArenaAllocator.init(gpa); @@ -3027,8 +3038,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { const is_inline = decl_tv.ty.fnCallingConvention() == .Inline; if (!is_inline and decl_tv.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. + // 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_func = func }); if (type_changed and mod.emit_h != null) { @@ -3387,6 +3398,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi new_decl.has_align = has_align; new_decl.has_linksection = has_linksection; new_decl.zir_decl_index = @intCast(u32, decl_sub_index); + new_decl.alive = true; // This Decl corresponds to an AST node and therefore always alive. return; } gpa.free(decl_name); @@ -3526,6 +3538,43 @@ pub fn clearDecl( decl.analysis = .unreferenced; } +pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void { + log.debug("deleteUnusedDecl {*} ({s})", .{ decl, decl.name }); + + // TODO: remove `allocateDeclIndexes` and make the API that the linker backends + // are required to notice the first time `updateDecl` happens and keep track + // of it themselves. However they can rely on getting a `freeDecl` call if any + // `updateDecl` or `updateFunc` calls happen. This will allow us to avoid any call + // into the linker backend here, since the linker backend will never have been told + // about the Decl in the first place. + // Until then, we did call `allocateDeclIndexes` on this anonymous Decl and so we + // must call `freeDecl` in the linker backend now. + if (decl.has_tv) { + if (decl.ty.hasCodeGenBits()) { + mod.comp.bin_file.freeDecl(decl); + } + } + + const dependants = decl.dependants.keys(); + assert(dependants[0].namespace.anon_decls.swapRemove(decl)); + + for (dependants) |dep| { + dep.removeDependency(decl); + } + + for (decl.dependencies.keys()) |dep| { + dep.removeDependant(decl); + } + decl.destroy(mod); +} + +pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void { + log.debug("deleteAnonDecl {*} ({s})", .{ decl, decl.name }); + const scope_decl = scope.ownerDecl().?; + assert(scope_decl.namespace.anon_decls.swapRemove(decl)); + decl.destroy(mod); +} + /// Delete all the Export objects that are caused by this Decl. Re-analysis of /// this Decl will cause them to be re-created (or not). fn deleteDeclExports(mod: *Module, decl: *Decl) void { @@ -3713,6 +3762,7 @@ fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node .is_exported = false, .has_linksection = false, .has_align = false, + .alive = false, }; return new_decl; } @@ -3802,12 +3852,6 @@ pub fn analyzeExport( errdefer de_gop.value_ptr.* = mod.gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1); } -pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void { - const scope_decl = scope.ownerDecl().?; - assert(scope_decl.namespace.anon_decls.swapRemove(decl)); - decl.destroy(mod); -} - /// Takes ownership of `name` even if it returns an error. pub fn createAnonymousDeclNamed( mod: *Module, diff --git a/src/Sema.zig b/src/Sema.zig index 95baae7e92..0da38d9a76 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -696,7 +696,7 @@ fn resolveMaybeUndefVal( ) CompileError!?Value { const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; if (val.tag() == .variable) { - return sema.failWithNeededComptime(block, src); + return null; } return val; } @@ -2917,12 +2917,13 @@ fn zirOptionalPayloadPtr( const child_pointer = try Module.simplePtrType(sema.arena, child_type, !optional_ptr_ty.isConstPtr(), .One); if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| { - const val = try pointer_val.pointerDeref(sema.arena); - if (val.isNull()) { - return sema.mod.fail(&block.base, src, "unable to unwrap null", .{}); + if (try pointer_val.pointerDeref(sema.arena)) |val| { + if (val.isNull()) { + return sema.mod.fail(&block.base, src, "unable to unwrap null", .{}); + } + // The same Value represents the pointer to the optional and the payload. + return sema.addConstant(child_pointer, pointer_val); } - // The same Value represents the pointer to the optional and the payload. - return sema.addConstant(child_pointer, pointer_val); } try sema.requireRuntimeBlock(block, src); @@ -3027,14 +3028,15 @@ fn zirErrUnionPayloadPtr( const operand_pointer_ty = try Module.simplePtrType(sema.arena, payload_ty, !operand_ty.isConstPtr(), .One); if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { - const val = try pointer_val.pointerDeref(sema.arena); - if (val.getError()) |name| { - return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); + if (try pointer_val.pointerDeref(sema.arena)) |val| { + if (val.getError()) |name| { + return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); + } + return sema.addConstant( + operand_pointer_ty, + try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), + ); } - return sema.addConstant( - operand_pointer_ty, - try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), - ); } try sema.requireRuntimeBlock(block, src); @@ -3086,10 +3088,11 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co const result_ty = operand_ty.elemType().errorUnionSet(); if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { - const val = try pointer_val.pointerDeref(sema.arena); - assert(val.getError() != null); - const data = val.castTag(.error_union).?.data; - return sema.addConstant(result_ty, data); + if (try pointer_val.pointerDeref(sema.arena)) |val| { + assert(val.getError() != null); + const data = val.castTag(.error_union).?.data; + return sema.addConstant(result_ty, data); + } } try sema.requireRuntimeBlock(block, src); @@ -4920,10 +4923,13 @@ fn analyzeArithmetic( log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value }); return sema.addConstant(scalar_type, value); + } else { + try sema.requireRuntimeBlock(block, rhs_src); } + } else { + try sema.requireRuntimeBlock(block, lhs_src); } - try sema.requireRuntimeBlock(block, src); const air_tag: Air.Inst.Tag = switch (zir_tag) { .add => .add, .addwrap => .addwrap, @@ -6811,16 +6817,10 @@ fn fieldPtr( if (mem.eql(u8, field_name, "len")) { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), + )); } else { return mod.fail( &block.base, @@ -6867,16 +6867,10 @@ fn fieldPtr( if (mem.eql(u8, field_name, "len")) { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), + )); } else { return mod.fail( &block.base, @@ -6915,16 +6909,10 @@ fn fieldPtr( var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - child_type, - try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + child_type, + try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), + )); }, .Struct, .Opaque, .Union => { if (child_type.getNamespace()) |namespace| { @@ -6971,16 +6959,10 @@ fn fieldPtr( const field_index_u32 = @intCast(u32, field_index); var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - child_type, - try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + child_type, + try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), + )); }, else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), } @@ -7671,21 +7653,18 @@ fn analyzeRef( operand: Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const operand_ty = sema.typeOf(operand); - const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - ptr_type, - try Value.Tag.decl_ref.create( - sema.arena, - try anon_decl.finish(operand_ty, try val.copy(anon_decl.arena())), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + operand_ty, + try val.copy(anon_decl.arena()), + )); } try sema.requireRuntimeBlock(block, src); + const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); const alloc = try block.addTy(.alloc, ptr_type); try sema.storePtr(block, src, alloc, operand); return alloc; @@ -7703,11 +7682,10 @@ fn analyzeLoad( .Pointer => ptr_ty.elemType(), else => return sema.mod.fail(&block.base, ptr_src, "expected pointer, found '{}'", .{ptr_ty}), }; - if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| blk: { - if (ptr_val.tag() == .int_u64) - break :blk; // do it at runtime - - return sema.addConstant(elem_ty, try ptr_val.pointerDeref(sema.arena)); + if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { + if (try ptr_val.pointerDeref(sema.arena)) |elem_val| { + return sema.addConstant(elem_ty, elem_val); + } } try sema.requireRuntimeBlock(block, src); @@ -8215,6 +8193,36 @@ fn resolvePeerTypes( return sema.typeOf(chosen); } +pub fn resolveTypeLayout( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + ty: Type, +) CompileError!void { + switch (ty.zigTypeTag()) { + .Pointer => { + return sema.resolveTypeLayout(block, src, ty.elemType()); + }, + .Struct => { + const resolved_ty = try sema.resolveTypeFields(block, src, ty); + const struct_obj = resolved_ty.castTag(.@"struct").?.data; + switch (struct_obj.status) { + .none, .have_field_types => {}, + .field_types_wip, .layout_wip => { + return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty}); + }, + .have_layout => return, + } + struct_obj.status = .layout_wip; + for (struct_obj.fields.values()) |field| { + try sema.resolveTypeLayout(block, src, field.ty); + } + struct_obj.status = .have_layout; + }, + else => {}, + } +} + fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type { switch (ty.tag()) { .@"struct" => { @@ -8222,9 +8230,7 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type switch (struct_obj.status) { .none => {}, .field_types_wip => { - return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ - ty, - }); + return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty}); }, .have_field_types, .have_layout, .layout_wip => return ty, } diff --git a/src/codegen.zig b/src/codegen.zig index b7ba367d54..d16a87adca 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -184,6 +184,7 @@ pub fn generateSymbol( if (typed_value.val.castTag(.decl_ref)) |payload| { const decl = payload.data; if (decl.analysis != .complete) return error.AnalysisFail; + decl.alive = true; // TODO handle the dependency of this symbol on the decl's vaddr. // If the decl changes vaddr, then this symbol needs to get regenerated. const vaddr = bin_file.getDeclVAddr(decl); @@ -4680,13 +4681,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, else => { if (typed_value.val.castTag(.decl_ref)) |payload| { + const decl = payload.data; + decl.alive = true; if (self.bin_file.cast(link.File.Elf)) |elf_file| { - 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.data; const got_addr = blk: { const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment; const got = seg.sections.items[macho_file.got_section_index.?]; @@ -4698,11 +4699,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - 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 if (self.bin_file.cast(link.File.Plan9)) |p9| { - const decl = payload.data; const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; return MCValue{ .memory = got_addr }; } else { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a8ec677753..826b73317c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -262,6 +262,7 @@ pub const DeclGen = struct { .one => try writer.writeAll("1"), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; + decl.alive = true; // Determine if we must pointer cast. assert(decl.has_tv); @@ -281,21 +282,7 @@ pub const DeclGen = struct { const decl = val.castTag(.extern_fn).?.data; try writer.print("{s}", .{decl.name}); }, - else => switch (t.ptrSize()) { - .Slice => unreachable, - .Many => unreachable, - .One => { - var arena = std.heap.ArenaAllocator.init(dg.module.gpa); - defer arena.deinit(); - - const elem_ty = t.elemType(); - const elem_val = try val.pointerDeref(&arena.allocator); - - try writer.writeAll("&"); - try dg.renderValue(writer, elem_ty, elem_val); - }, - .C => unreachable, - }, + else => unreachable, }, }, .Array => { @@ -421,6 +408,7 @@ pub const DeclGen = struct { .one => try writer.writeAll("1"), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; + decl.alive = true; // Determine if we must pointer cast. assert(decl.has_tv); @@ -433,11 +421,13 @@ pub const DeclGen = struct { } }, .function => { - const func = val.castTag(.function).?.data; - try writer.print("{s}", .{func.owner_decl.name}); + const decl = val.castTag(.function).?.data.owner_decl; + decl.alive = true; + try writer.print("{s}", .{decl.name}); }, .extern_fn => { const decl = val.castTag(.extern_fn).?.data; + decl.alive = true; try writer.print("{s}", .{decl.name}); }, else => unreachable, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0e9a572bea..961ed7ee99 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -673,17 +673,21 @@ pub const DeclGen = struct { } fn genTypedValue(self: *DeclGen, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - const llvm_type = try self.llvmType(tv.ty); - - if (tv.val.isUndef()) + if (tv.val.isUndef()) { + const llvm_type = try self.llvmType(tv.ty); return llvm_type.getUndef(); + } switch (tv.ty.zigTypeTag()) { - .Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(), + .Bool => { + const llvm_type = try self.llvmType(tv.ty); + 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); + const llvm_type = try self.llvmType(tv.ty); if (bigint.eqZero()) return llvm_type.constNull(); if (bigint.limbs.len != 1) { @@ -698,12 +702,17 @@ pub const DeclGen = struct { .Pointer => switch (tv.val.tag()) { .decl_ref => { const decl = tv.val.castTag(.decl_ref).?.data; + decl.alive = true; const val = try self.resolveGlobalDecl(decl); + const llvm_type = try self.llvmType(tv.ty); return val.constBitCast(llvm_type); }, .variable => { - const variable = tv.val.castTag(.variable).?.data; - const val = try self.resolveGlobalDecl(variable.owner_decl); + const decl = tv.val.castTag(.variable).?.data.owner_decl; + decl.alive = true; + const val = try self.resolveGlobalDecl(decl); + const llvm_var_type = try self.llvmType(tv.ty); + const llvm_type = llvm_var_type.pointerType(0); return val.constBitCast(llvm_type); }, .slice => { @@ -783,6 +792,7 @@ pub const DeclGen = struct { .decl_ref => tv.val.castTag(.decl_ref).?.data, else => unreachable, }; + fn_decl.alive = true; return self.resolveLlvmFunction(fn_decl); }, .ErrorSet => { @@ -903,9 +913,7 @@ pub const FuncGen = struct { return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }); } const inst_index = Air.refToIndex(inst).?; - if (self.func_inst_table.get(inst_index)) |value| return value; - - return self.todo("implement global llvm values (or the value is not in the func_inst_table table)", .{}); + return self.func_inst_table.get(inst_index).?; } fn genBody(self: *FuncGen, body: []const Air.Inst.Index) error{ OutOfMemory, CodegenFail }!void { @@ -966,8 +974,8 @@ pub const FuncGen = struct { .struct_field_ptr => try self.airStructFieldPtr(inst), .struct_field_val => try self.airStructFieldVal(inst), - .slice_elem_val => try self.airSliceElemVal(inst, false), - .ptr_slice_elem_val => try self.airSliceElemVal(inst, true), + .slice_elem_val => try self.airSliceElemVal(inst), + .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), .optional_payload => try self.airOptionalPayload(inst, false), .optional_payload_ptr => try self.airOptionalPayload(inst, true), @@ -1170,11 +1178,20 @@ pub const FuncGen = struct { return self.builder.buildExtractValue(operand, index, ""); } - fn airSliceElemVal( - self: *FuncGen, - inst: Air.Inst.Index, - operand_is_ptr: bool, - ) !?*const llvm.Value { + fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const base_ptr = self.builder.buildExtractValue(lhs, 0, ""); + const indices: [1]*const llvm.Value = .{rhs}; + const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + return self.builder.buildLoad(ptr, ""); + } + + fn airPtrSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1182,7 +1199,7 @@ pub const FuncGen = struct { const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); - const base_ptr = if (!operand_is_ptr) lhs else ptr: { + const base_ptr = ptr: { const index_type = self.context.intType(32); const indices: [2]*const llvm.Value = .{ index_type.constNull(), diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 37cc6bc59c..2f1632e0fc 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -1016,6 +1016,7 @@ pub const Context = struct { .Pointer => { if (val.castTag(.decl_ref)) |payload| { const decl = payload.data; + decl.alive = true; // offset into the offset table within the 'data' section const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8; diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 135b59f82b..3b2aae85bc 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -224,7 +224,9 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void { const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; - assert(self.got_len == self.fn_decl_table.count() + self.data_decl_table.count()); + // TODO I changed this assert from == to >= but this code all needs to be audited; see + // the comment in `freeDecl`. + assert(self.got_len >= self.fn_decl_table.count() + self.data_decl_table.count()); const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8; var got_table = try self.base.allocator.alloc(u8, got_size); defer self.base.allocator.free(got_table); @@ -358,11 +360,18 @@ fn addDeclExports( } pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { + // TODO this is not the correct check for being function body, + // it could just be a function pointer. + // TODO audit the lifetimes of decls table entries. It's possible to get + // allocateDeclIndexes and then freeDecl without any updateDecl in between. + // However that is planned to change, see the TODO comment in Module.zig + // in the deleteUnusedDecl function. const is_fn = (decl.ty.zigTypeTag() == .Fn); - if (is_fn) - assert(self.fn_decl_table.swapRemove(decl)) - else - assert(self.data_decl_table.swapRemove(decl)); + if (is_fn) { + _ = self.fn_decl_table.swapRemove(decl); + } else { + _ = self.data_decl_table.swapRemove(decl); + } } pub fn updateDeclExports( diff --git a/src/value.zig b/src/value.zig index d3317ef31d..32be34ee2c 100644 --- a/src/value.zig +++ b/src/value.zig @@ -103,6 +103,7 @@ pub const Value = extern union { /// Represents a comptime variables storage. comptime_alloc, /// Represents a pointer to a decl, not the value of the decl. + /// When machine codegen backend sees this, it must set the Decl's `alive` field to true. decl_ref, elem_ptr, field_ptr, @@ -1346,28 +1347,48 @@ pub const Value = extern union { /// Asserts the value is a pointer and dereferences it. /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis. - pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { - return switch (self.tag()) { + pub fn pointerDeref( + self: Value, + allocator: *Allocator, + ) error{ AnalysisFail, OutOfMemory }!?Value { + const sub_val: Value = switch (self.tag()) { .comptime_alloc => self.castTag(.comptime_alloc).?.data.val, - .decl_ref => self.castTag(.decl_ref).?.data.value(), - .elem_ptr => { + .decl_ref => try self.castTag(.decl_ref).?.data.value(), + .elem_ptr => blk: { 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); + const array_val = (try elem_ptr.array_ptr.pointerDeref(allocator)) orelse return null; + break :blk try array_val.elemValue(allocator, elem_ptr.index); }, - .field_ptr => { + .field_ptr => blk: { const field_ptr = self.castTag(.field_ptr).?.data; - const container_val = try field_ptr.container_ptr.pointerDeref(allocator); - return container_val.fieldValue(allocator, field_ptr.field_index); + const container_val = (try field_ptr.container_ptr.pointerDeref(allocator)) orelse return null; + break :blk try container_val.fieldValue(allocator, field_ptr.field_index); }, - .eu_payload_ptr => { + .eu_payload_ptr => blk: { const err_union_ptr = self.castTag(.eu_payload_ptr).?.data; - const err_union_val = try err_union_ptr.pointerDeref(allocator); - return err_union_val.castTag(.error_union).?.data; + const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null; + break :blk err_union_val.castTag(.error_union).?.data; }, + .zero, + .one, + .int_u64, + .int_i64, + .int_big_positive, + .int_big_negative, + .variable, + .extern_fn, + .function, + => return null, + else => unreachable, }; + if (sub_val.tag() == .variable) { + // This would be loading a runtime value at compile-time so we return + // the indicator that this pointer dereference requires being done at runtime. + return null; + } + return sub_val; } pub fn sliceLen(val: Value) u64 { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 6427e2e3b8..4f7d80d1fa 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -49,7 +49,7 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn foo() callconv(y) c_int { \\ return 0; \\} - \\var y: i32 = 1234; + \\var y: @import("std").builtin.CallingConvention = .C; , &.{ ":2:22: error: unable to resolve comptime value", ":5:26: error: unable to resolve comptime value", -- cgit v1.2.3 From ddf14323ea9b2c75ac5ed286525d27730a192b53 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Aug 2021 16:13:58 -0700 Subject: stage2: implement `@truncate` --- src/Air.zig | 11 +- src/Liveness.zig | 1 + src/Module.zig | 238 ------------------------------------------ src/Sema.zig | 104 ++++++++++++------ src/codegen.zig | 14 +++ src/codegen/c.zig | 15 ++- src/codegen/llvm.zig | 11 ++ src/codegen/llvm/bindings.zig | 8 ++ src/print_air.zig | 1 + src/value.zig | 238 ++++++++++++++++++++++++++++++++++++++++++ test/behavior/basic.zig | 11 ++ test/behavior/misc.zig | 7 -- 12 files changed, 380 insertions(+), 279 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Air.zig b/src/Air.zig index d202c079bc..d923bf0b02 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -206,10 +206,16 @@ pub const Inst = struct { /// Convert from one float type to another. /// Uses the `ty_op` field. floatcast, - /// TODO audit uses of this. We should have explicit instructions for integer - /// widening and truncating. + /// Returns an integer with a different type than the operand. The new type may have + /// fewer, the same, or more bits than the operand type. However, the instruction + /// guarantees that the same integer value fits in both types. + /// See `trunc` for integer truncation. /// Uses the `ty_op` field. intcast, + /// Truncate higher bits from an integer, resulting in an integer with the same + /// sign but an equal or smaller number of bits. + /// Uses the `ty_op` field. + trunc, /// ?T => T. If the value is null, undefined behavior. /// Uses the `ty_op` field. optional_payload, @@ -452,6 +458,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .load, .floatcast, .intcast, + .trunc, .optional_payload, .optional_payload_ptr, .wrap_optional, diff --git a/src/Liveness.zig b/src/Liveness.zig index 7ba062fa31..4e22febc5a 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -264,6 +264,7 @@ fn analyzeInst( .load, .floatcast, .intcast, + .trunc, .optional_payload, .optional_payload_ptr, .wrap_optional, diff --git a/src/Module.zig b/src/Module.zig index d87f20621c..84b721369d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4033,244 +4033,6 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) Co return error.AnalysisFail; } -pub fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try allocator.alloc( - std.math.big.Limb, - std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, - ); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - result_bigint.add(lhs_bigint, rhs_bigint); - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - 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 { - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try allocator.alloc( - std.math.big.Limb, - std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, - ); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - result_bigint.sub(lhs_bigint, rhs_bigint); - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - 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 intDiv(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs_q = try allocator.alloc( - std.math.big.Limb, - lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, - ); - const limbs_r = try allocator.alloc( - std.math.big.Limb, - lhs_bigint.limbs.len, - ); - const limbs_buffer = try allocator.alloc( - std.math.big.Limb, - std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), - ); - var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; - var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; - result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); - const result_limbs = result_q.limbs[0..result_q.len]; - - if (result_q.positive) { - return Value.Tag.int_big_positive.create(allocator, result_limbs); - } else { - return Value.Tag.int_big_negative.create(allocator, result_limbs); - } -} - -pub fn intMul(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try allocator.alloc( - std.math.big.Limb, - lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, - ); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - var limbs_buffer = try allocator.alloc( - std.math.big.Limb, - std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1), - ); - defer allocator.free(limbs_buffer); - result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator); - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - 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( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - 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); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val + rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val + rhs_val); - }, - .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, - } -} - -pub fn floatSub( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - 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); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val - rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val - rhs_val); - }, - .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, - } -} - -pub fn floatDiv( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - 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); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val / rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val / rhs_val); - }, - .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, - } -} - -pub fn floatMul( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - 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); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val * rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val * rhs_val); - }, - .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, - } -} - pub fn simplePtrType( arena: *Allocator, elem_ty: Type, diff --git a/src/Sema.zig b/src/Sema.zig index 5ad590be6a..6b281f8569 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3479,27 +3479,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs); const operand = sema.resolveInst(extra.rhs); - const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { - .ComptimeInt => true, - .Int => false, - else => return sema.mod.fail( - &block.base, - dest_ty_src, - "expected integer type, found '{}'", - .{dest_type}, - ), - }; - - const operand_ty = sema.typeOf(operand); - switch (operand_ty.zigTypeTag()) { - .ComptimeInt, .Int => {}, - else => return sema.mod.fail( - &block.base, - operand_src, - "expected integer type, found '{}'", - .{operand_ty}, - ), - } + const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_type); + _ = try sema.requireIntegerType(block, operand_src, sema.typeOf(operand)); if (try sema.isComptimeKnown(block, operand_src, operand)) { return sema.coerce(block, dest_type, operand, operand_src); @@ -4951,30 +4932,30 @@ fn analyzeArithmetic( const value = switch (zir_tag) { .add => blk: { const val = if (is_int) - try Module.intAdd(sema.arena, lhs_val, rhs_val) + try lhs_val.intAdd(rhs_val, sema.arena) else - try Module.floatAdd(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena); break :blk val; }, .sub => blk: { const val = if (is_int) - try Module.intSub(sema.arena, lhs_val, rhs_val) + try lhs_val.intSub(rhs_val, sema.arena) else - try Module.floatSub(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatSub(rhs_val, scalar_type, sema.arena); break :blk val; }, .div => blk: { const val = if (is_int) - try Module.intDiv(sema.arena, lhs_val, rhs_val) + try lhs_val.intDiv(rhs_val, sema.arena) else - try Module.floatDiv(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena); break :blk val; }, .mul => blk: { const val = if (is_int) - try Module.intMul(sema.arena, lhs_val, rhs_val) + try lhs_val.intMul(rhs_val, sema.arena) else - try Module.floatMul(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatMul(rhs_val, scalar_type, sema.arena); break :blk val; }, else => return sema.mod.fail(&block.base, src, "TODO Implement arithmetic operand '{s}'", .{@tagName(zir_tag)}), @@ -6173,7 +6154,62 @@ fn zirPtrCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr fn zirTruncate(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirTruncate", .{}); + const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); + const operand = sema.resolveInst(extra.rhs); + const operand_ty = sema.typeOf(operand); + const mod = sema.mod; + const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_ty); + const src_is_comptime_int = try sema.requireIntegerType(block, operand_src, operand_ty); + + if (dest_is_comptime_int) { + return sema.coerce(block, dest_ty, operand, operand_src); + } + + const target = mod.getTarget(); + const src_info = operand_ty.intInfo(target); + const dest_info = dest_ty.intInfo(target); + + if (src_info.bits == 0 or dest_info.bits == 0) { + return sema.addConstant(dest_ty, Value.initTag(.zero)); + } + + if (!src_is_comptime_int) { + if (src_info.signedness != dest_info.signedness) { + return mod.fail(&block.base, operand_src, "expected {s} integer type, found '{}'", .{ + @tagName(dest_info.signedness), operand_ty, + }); + } + if (src_info.bits > 0 and src_info.bits < dest_info.bits) { + const msg = msg: { + const msg = try mod.errMsg( + &block.base, + src, + "destination type '{}' has more bits than source type '{}'", + .{ dest_ty, operand_ty }, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(&block.base, dest_ty_src, msg, "destination type has {d} bits", .{ + dest_info.bits, + }); + try mod.errNote(&block.base, operand_src, msg, "source type has {d} bits", .{ + src_info.bits, + }); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + } + } + + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(dest_ty); + return sema.addConstant(dest_ty, try val.intTrunc(sema.arena, dest_info.bits)); + } + + try sema.requireRuntimeBlock(block, src); + return block.addTyOp(.trunc, dest_ty, operand); } fn zirAlignCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6594,6 +6630,14 @@ fn requireRuntimeBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void try sema.requireFunctionBlock(block, src); } +fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !bool { + switch (ty.zigTypeTag()) { + .ComptimeInt => return true, + .Int => return false, + else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}), + } +} + fn validateVarType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void { if (!ty.isValidVarType(false)) { return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{ty}); diff --git a/src/codegen.zig b/src/codegen.zig index 8c56ab4431..3864479d2d 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -835,6 +835,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .dbg_stmt => try self.airDbgStmt(inst), .floatcast => try self.airFloatCast(inst), .intcast => try self.airIntCast(inst), + .trunc => try self.airTrunc(inst), .bool_to_int => try self.airBoolToInt(inst), .is_non_null => try self.airIsNonNull(inst), .is_non_null_ptr => try self.airIsNonNullPtr(inst), @@ -1109,6 +1110,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } + fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + if (self.liveness.isUnused(inst)) + return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none }); + + const operand = try self.resolveInst(ty_op.operand); + _ = operand; + const result: MCValue = switch (arch) { + else => return self.fail("TODO implement trunc for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + fn airBoolToInt(self: *Self, inst: Air.Inst.Index) !void { const un_op = self.air.instructions.items(.data)[inst].un_op; const operand = try self.resolveInst(un_op); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 826b73317c..22420aca45 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -900,6 +900,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .call => try airCall(o, inst), .dbg_stmt => try airDbgStmt(o, inst), .intcast => try airIntCast(o, inst), + .trunc => try airTrunc(o, inst), .bool_to_int => try airBoolToInt(o, inst), .load => try airLoad(o, inst), .ret => try airRet(o, inst), @@ -1038,7 +1039,7 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { return CValue.none; const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const from = try o.resolveInst(ty_op.operand); + const operand = try o.resolveInst(ty_op.operand); const writer = o.writer(); const inst_ty = o.air.typeOfIndex(inst); @@ -1046,11 +1047,21 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { try writer.writeAll(" = ("); try o.dg.renderType(writer, inst_ty); try writer.writeAll(")"); - try o.writeCValue(writer, from); + try o.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } +fn airTrunc(o: *Object, inst: Air.Inst.Index) !CValue { + if (o.liveness.isUnused(inst)) + return CValue.none; + + const ty_op = o.air.instructions.items(.data)[inst].ty_op; + const operand = try o.resolveInst(ty_op.operand); + _ = operand; + return o.dg.fail("TODO: C backend: airTrunc", .{}); +} + fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue { if (o.liveness.isUnused(inst)) return CValue.none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 961ed7ee99..8b3edcfe23 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -960,6 +960,7 @@ pub const FuncGen = struct { .call => try self.airCall(inst), .cond_br => try self.airCondBr(inst), .intcast => try self.airIntCast(inst), + .trunc => try self.airTrunc(inst), .floatcast => try self.airFloatCast(inst), .ptrtoint => try self.airPtrToInt(inst), .load => try self.airLoad(inst), @@ -1615,6 +1616,16 @@ pub const FuncGen = struct { return self.builder.buildIntCast2(operand, try self.dg.llvmType(inst_ty), llvm.Bool.fromBool(signed), ""); } + fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst)); + return self.builder.buildTrunc(operand, dest_llvm_ty, ""); + } + fn airFloatCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 0977d6128d..4af3cadd84 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -423,6 +423,14 @@ pub const Builder = opaque { Idx: c_uint, Name: [*:0]const u8, ) *const Value; + + pub const buildTrunc = LLVMBuildTrunc; + extern fn LLVMBuildTrunc( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; }; pub const IntPredicate = enum(c_int) { diff --git a/src/print_air.zig b/src/print_air.zig index 5d77c303bb..00317b26e8 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -151,6 +151,7 @@ const Writer = struct { .load, .floatcast, .intcast, + .trunc, .optional_payload, .optional_payload_ptr, .wrap_optional, diff --git a/src/value.zig b/src/value.zig index a0a7c0650c..134b51e494 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1407,6 +1407,244 @@ pub const Value = extern union { }; } + pub fn intAdd(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try allocator.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.add(lhs_bigint, rhs_bigint); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + 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(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try allocator.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.sub(lhs_bigint, rhs_bigint); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + 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 intDiv(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs_q = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + const limbs_r = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len, + ); + const limbs_buffer = try allocator.alloc( + std.math.big.Limb, + std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; + var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; + result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); + const result_limbs = result_q.limbs[0..result_q.len]; + + if (result_q.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intMul(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + var limbs_buffer = try allocator.alloc( + std.math.big.Limb, + std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1), + ); + defer allocator.free(limbs_buffer); + result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + 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 intTrunc(val: Value, arena: *Allocator, bits: u16) !Value { + const x = val.toUnsignedInt(); // TODO: implement comptime truncate on big ints + if (bits == 64) return val; + const mask = (@as(u64, 1) << @intCast(u6, bits)) - 1; + const truncated = x & mask; + return Tag.int_u64.create(arena, truncated); + } + + pub fn floatAdd( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + 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); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val + rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val + rhs_val); + }, + .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, + } + } + + pub fn floatSub( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + 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); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val - rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val - rhs_val); + }, + .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, + } + } + + pub fn floatDiv( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + 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); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val / rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val / rhs_val); + }, + .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, + } + } + + pub fn floatMul( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + 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); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val * rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val * rhs_val); + }, + .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, + } + } + /// This type is not copyable since it may contain pointers to its inner data. pub const Payload = struct { tag: Tag, diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 8f0f20cdf5..87de1c9fe5 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -1,3 +1,6 @@ +const std = @import("std"); +const expect = std.testing.expect; + // normal comment /// this is a documentation comment @@ -7,3 +10,11 @@ fn emptyFunctionWithComments() void {} test "empty function with comments" { emptyFunctionWithComments(); } + +test "truncate" { + try expect(testTruncate(0x10fd) == 0xfd); + comptime try expect(testTruncate(0x10fd) == 0xfd); +} +fn testTruncate(x: u32) u8 { + return @truncate(u8, x); +} diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig index ba38d6327c..700e043313 100644 --- a/test/behavior/misc.zig +++ b/test/behavior/misc.zig @@ -5,13 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings; const mem = std.mem; const builtin = @import("builtin"); -test "truncate" { - try expect(testTruncate(0x10fd) == 0xfd); -} -fn testTruncate(x: u32) u8 { - return @truncate(u8, x); -} - fn first4KeysOfHomeRow() []const u8 { return "aoeu"; } -- cgit v1.2.3 From d4468affb751668e156230c32b29c84684825b4f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Aug 2021 21:11:31 -0700 Subject: stage2 generics improvements: anytype and param type exprs AstGen result locations now have a `coerced_ty` tag which is the same as `ty` except it assumes that Sema will do a coercion, so it does not redundantly add an `as` instruction into the ZIR code. This results in cleaner ZIR and about a 14% reduction of ZIR bytes. param and param_comptime ZIR instructions now have a block body for their type expressions. This allows Sema to skip evaluation of the block in the case that the parameter is comptime-provided. It also allows a new mechanism to function: when evaluating type expressions of generic functions, if it would depend on another parameter, it returns `error.GenericPoison` which bubbles up and then is caught by the param/param_comptime instruction and then handled. This allows parameters to be evaluated independently so that the type info for functions which have comptime or anytype parameters will still have types populated for parameters that do not depend on values of previous parameters (because evaluation of their param blocks will return successfully instead of `error.GenericPoison`). It also makes iteration over the block that contains function parameters slightly more efficient since it now only contains the param instructions. Finally, it fixes the case where a generic function type expression contains a function prototype. Formerly, this situation would cause shared state to clobber each other; now it is in a proper tree structure so that can't happen. This fix also required adding a field to Sema `comptime_args_fn_inst` to make sure that the `comptime_args` field passed into Sema is applied to the correct `func` instruction. Source location for `node_offset_asm_ret_ty` is fixed; it was pointing at the asm output name rather than the return type as intended. Generic function instantiation is fixed, notably with respect to parameter type expressions that depend on previous parameters, and with respect to types which must be always comptime-known. This involves passing all the comptime arguments at a callsite of a generic function, and allowing the generic function semantic analysis to coerce the values to the proper types (since it has access to the evaluated parameter type expressions) and then decide based on the type whether the parameter is runtime known or not. In the case of explicitly marked `comptime` parameters, there is a check at the semantic analysis of the `call` instruction. Semantic analysis of `call` instructions does type coercion on the arguments, which is needed both for generic functions and to make up for using `coerced_ty` result locations (mentioned above). Tasks left in this branch: * Implement the memoization table. * Add test coverage. * Improve error reporting and source locations for compile errors. --- BRANCH_TODO | 4 - src/AstGen.zig | 71 +++++---- src/Compilation.zig | 2 +- src/Module.zig | 52 +++++-- src/Sema.zig | 428 ++++++++++++++++++++++++++++++++------------------- src/Zir.zig | 32 +++- src/codegen/llvm.zig | 4 + src/type.zig | 116 ++++++++++++++ src/value.zig | 55 ++----- test/cases.zig | 2 +- 10 files changed, 519 insertions(+), 247 deletions(-) delete mode 100644 BRANCH_TODO (limited to 'src/codegen/llvm.zig') diff --git a/BRANCH_TODO b/BRANCH_TODO deleted file mode 100644 index e8606332d7..0000000000 --- a/BRANCH_TODO +++ /dev/null @@ -1,4 +0,0 @@ -* memoize the instantiation in a table -* expressions that depend on comptime stuff need a poison value to use for - types when generating the generic function type -* comptime anytype diff --git a/src/AstGen.zig b/src/AstGen.zig index 7534afe961..493e0a75f4 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -195,6 +195,9 @@ pub const ResultLoc = union(enum) { none_or_ref, /// The expression will be coerced into this type, but it will be evaluated as an rvalue. ty: Zir.Inst.Ref, + /// Same as `ty` but it is guaranteed that Sema will additionall perform the coercion, + /// so no `as` instruction needs to be emitted. + coerced_ty: Zir.Inst.Ref, /// The expression must store its result into this typed pointer. The result instruction /// from the expression must be ignored. ptr: Zir.Inst.Ref, @@ -225,7 +228,7 @@ pub const ResultLoc = union(enum) { fn strategy(rl: ResultLoc, block_scope: *GenZir) Strategy { switch (rl) { // In this branch there will not be any store_to_block_ptr instructions. - .discard, .none, .none_or_ref, .ty, .ref => return .{ + .discard, .none, .none_or_ref, .ty, .coerced_ty, .ref => return .{ .tag = .break_operand, .elide_store_to_block_ptr_instructions = false, }, @@ -260,13 +263,14 @@ pub const ResultLoc = union(enum) { pub const align_rl: ResultLoc = .{ .ty = .u16_type }; pub const bool_rl: ResultLoc = .{ .ty = .bool_type }; pub const type_rl: ResultLoc = .{ .ty = .type_type }; +pub const coerced_type_rl: ResultLoc = .{ .coerced_ty = .type_type }; fn typeExpr(gz: *GenZir, scope: *Scope, type_node: ast.Node.Index) InnerError!Zir.Inst.Ref { const prev_force_comptime = gz.force_comptime; gz.force_comptime = true; defer gz.force_comptime = prev_force_comptime; - return expr(gz, scope, .{ .ty = .type_type }, type_node); + return expr(gz, scope, coerced_type_rl, type_node); } /// Same as `expr` but fails with a compile error if the result type is `noreturn`. @@ -1079,16 +1083,19 @@ fn fnProtoExpr( .param_anytype; _ = try gz.addStrTok(tag, param_name, name_token); } else { + const gpa = astgen.gpa; const param_type_node = param.type_expr; assert(param_type_node != 0); - const param_type = try expr(gz, scope, type_rl, param_type_node); + var param_gz = gz.makeSubBlock(scope); + defer param_gz.instructions.deinit(gpa); + const param_type = try expr(¶m_gz, scope, coerced_type_rl, param_type_node); + const param_inst_expected = @intCast(u32, astgen.instructions.len + 1); + _ = try param_gz.addBreak(.break_inline, param_inst_expected, param_type); const main_tokens = tree.nodes.items(.main_token); const name_token = param.name_token orelse main_tokens[param_type_node]; const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param; - _ = try gz.addPlTok(tag, name_token, Zir.Inst.Param{ - .name = param_name, - .ty = param_type, - }); + const param_inst = try gz.addParam(tag, name_token, param_name, param_gz.instructions.items); + assert(param_inst_expected == param_inst); } } break :is_var_args false; @@ -1219,7 +1226,7 @@ fn arrayInitExpr( return arrayInitExprRlNone(gz, scope, node, array_init.ast.elements, .array_init_anon); } }, - .ty => |ty_inst| { + .ty, .coerced_ty => |ty_inst| { if (types.array != .none) { const result = try arrayInitExprRlTy(gz, scope, node, array_init.ast.elements, types.elem, .array_init); return rvalue(gz, rl, result, node); @@ -1388,7 +1395,7 @@ fn structInitExpr( return structInitExprRlNone(gz, scope, node, struct_init, .struct_init_anon); } }, - .ty => |ty_inst| { + .ty, .coerced_ty => |ty_inst| { if (struct_init.ast.type_expr == 0) { return structInitExprRlTy(gz, scope, node, struct_init, ty_inst, .struct_init); } @@ -2617,7 +2624,7 @@ fn assignOp( const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs); const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node); - const rhs = try expr(gz, scope, .{ .ty = lhs_type }, node_datas[infix_node].rhs); + const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs); const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{ .lhs = lhs, @@ -2953,14 +2960,18 @@ fn fnDecl( } else param: { const param_type_node = param.type_expr; assert(param_type_node != 0); - const param_type = try expr(&decl_gz, params_scope, type_rl, param_type_node); + var param_gz = decl_gz.makeSubBlock(scope); + defer param_gz.instructions.deinit(gpa); + const param_type = try expr(¶m_gz, params_scope, coerced_type_rl, param_type_node); + const param_inst_expected = @intCast(u32, astgen.instructions.len + 1); + _ = try param_gz.addBreak(.break_inline, param_inst_expected, param_type); + const main_tokens = tree.nodes.items(.main_token); const name_token = param.name_token orelse main_tokens[param_type_node]; const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param; - break :param try decl_gz.addPlTok(tag, name_token, Zir.Inst.Param{ - .name = param_name, - .ty = param_type, - }); + const param_inst = try decl_gz.addParam(tag, name_token, param_name, param_gz.instructions.items); + assert(param_inst_expected == param_inst); + break :param indexToRef(param_inst); }; if (param_name == 0) continue; @@ -6758,7 +6769,7 @@ fn as( ) InnerError!Zir.Inst.Ref { const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { - .none, .none_or_ref, .discard, .ref, .ty => { + .none, .none_or_ref, .discard, .ref, .ty, .coerced_ty => { const result = try reachableExpr(gz, scope, .{ .ty = dest_type }, rhs, node); return rvalue(gz, rl, result, node); }, @@ -6781,7 +6792,7 @@ fn unionInit( const union_type = try typeExpr(gz, scope, params[0]); const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); switch (rl) { - .none, .none_or_ref, .discard, .ref, .ty, .inferred_ptr => { + .none, .none_or_ref, .discard, .ref, .ty, .coerced_ty, .inferred_ptr => { _ = try gz.addPlNode(.field_type_ref, params[1], Zir.Inst.FieldTypeRef{ .container_type = union_type, .field_name = field_name, @@ -6867,7 +6878,7 @@ fn bitCast( const astgen = gz.astgen; const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { - .none, .none_or_ref, .discard, .ty => { + .none, .none_or_ref, .discard, .ty, .coerced_ty => { const operand = try expr(gz, scope, .none, rhs); const result = try gz.addPlNode(.bitcast, node, Zir.Inst.Bin{ .lhs = dest_type, @@ -7677,7 +7688,7 @@ fn callExpr( .param_index = @intCast(u32, i), } }, }); - args[i] = try expr(gz, scope, .{ .ty = param_type }, param_node); + args[i] = try expr(gz, scope, .{ .coerced_ty = param_type }, param_node); } const modifier: std.builtin.CallOptions.Modifier = blk: { @@ -8370,7 +8381,7 @@ fn rvalue( src_node: ast.Node.Index, ) InnerError!Zir.Inst.Ref { switch (rl) { - .none, .none_or_ref => return result, + .none, .none_or_ref, .coerced_ty => return result, .discard => { // Emit a compile error for discarding error values. _ = try gz.addUnNode(.ensure_result_non_error, result, src_node); @@ -9042,7 +9053,7 @@ const GenZir = struct { // we emit ZIR for the block break instructions to have the result values, // and then rvalue() on that to pass the value to the result location. switch (parent_rl) { - .ty => |ty_inst| { + .ty, .coerced_ty => |ty_inst| { gz.rl_ty_inst = ty_inst; gz.break_result_loc = parent_rl; }, @@ -9425,18 +9436,26 @@ const GenZir = struct { return indexToRef(new_index); } - fn addPlTok( + fn addParam( gz: *GenZir, tag: Zir.Inst.Tag, /// Absolute token index. This function does the conversion to Decl offset. abs_tok_index: ast.TokenIndex, - extra: anytype, - ) !Zir.Inst.Ref { + name: u32, + body: []const u32, + ) !Zir.Inst.Index { const gpa = gz.astgen.gpa; try gz.instructions.ensureUnusedCapacity(gpa, 1); try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Param).Struct.fields.len + + body.len); + + const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Param{ + .name = name, + .body_len = @intCast(u32, body.len), + }); + gz.astgen.extra.appendSliceAssumeCapacity(body); - const payload_index = try gz.astgen.addExtra(extra); const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); gz.astgen.instructions.appendAssumeCapacity(.{ .tag = tag, @@ -9446,7 +9465,7 @@ const GenZir = struct { } }, }); gz.instructions.appendAssumeCapacity(new_index); - return indexToRef(new_index); + return new_index; } fn addExtendedPayload( diff --git a/src/Compilation.zig b/src/Compilation.zig index f8f8cea328..adb4940243 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2118,7 +2118,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor if (builtin.mode == .Debug and self.verbose_air) { std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name}); @import("print_air.zig").dump(gpa, air, decl.namespace.file_scope.zir, liveness); - std.debug.print("# End Function AIR: {s}:\n", .{decl.name}); + std.debug.print("# End Function AIR: {s}\n\n", .{decl.name}); } self.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) { diff --git a/src/Module.zig b/src/Module.zig index 184ea617b1..2c3e745c11 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1173,6 +1173,8 @@ pub const Scope = struct { /// for the one that will be the same for all Block instances. src_decl: *Decl, instructions: ArrayListUnmanaged(Air.Inst.Index), + // `param` instructions are collected here to be used by the `func` instruction. + params: std.ArrayListUnmanaged(Param) = .{}, label: ?*Label = null, inlining: ?*Inlining, /// If runtime_index is not 0 then one of these is guaranteed to be non null. @@ -1187,6 +1189,12 @@ pub const Scope = struct { /// when null, it is determined by build mode, changed by @setRuntimeSafety want_safety: ?bool = null, + const Param = struct { + /// `noreturn` means `anytype`. + ty: Type, + is_comptime: bool, + }; + /// This `Block` maps a block ZIR instruction to the corresponding /// AIR instruction for break instruction analysis. pub const Label = struct { @@ -1634,8 +1642,11 @@ pub const SrcLoc = struct { .@"asm" => tree.asmFull(node), else => unreachable, }; + const asm_output = full.outputs[0]; + const node_datas = tree.nodes.items(.data); + const ret_ty_node = node_datas[asm_output].lhs; const main_tokens = tree.nodes.items(.main_token); - const tok_index = main_tokens[full.outputs[0]]; + const tok_index = main_tokens[ret_ty_node]; const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, @@ -2099,7 +2110,20 @@ pub const LazySrcLoc = union(enum) { }; pub const SemaError = error{ OutOfMemory, AnalysisFail }; -pub const CompileError = error{ OutOfMemory, AnalysisFail, NeededSourceLocation }; +pub const CompileError = error{ + OutOfMemory, + /// When this is returned, the compile error for the failure has already been recorded. + AnalysisFail, + /// Returned when a compile error needed to be reported but a provided LazySrcLoc was set + /// to the `unneeded` tag. The source location was, in fact, needed. It is expected that + /// somewhere up the call stack, the operation will be retried after doing expensive work + /// to compute a source location. + NeededSourceLocation, + /// A Type or Value was needed to be used during semantic analysis, but it was not available + /// because the function is generic. This is only seen when analyzing the body of a param + /// instruction. + GenericPoison, +}; pub fn deinit(mod: *Module) void { const gpa = mod.gpa; @@ -2796,14 +2820,16 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) SemaError!void { } return error.AnalysisFail; }, - else => { + error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, + else => |e| { decl.analysis = .sema_failure_retryable; try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1); mod.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( mod.gpa, decl.srcLoc(), "unable to analyze: {s}", - .{@errorName(err)}, + .{@errorName(e)}, )); return error.AnalysisFail; }, @@ -2982,7 +3008,10 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { .inlining = null, .is_comptime = true, }; - defer block_scope.instructions.deinit(gpa); + defer { + block_scope.instructions.deinit(gpa); + block_scope.params.deinit(gpa); + } const zir_block_index = decl.zirBlockIndex(); const inst_data = zir_datas[zir_block_index].pl_node; @@ -3669,7 +3698,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType` try sema.inst_map.ensureUnusedCapacity(gpa, fn_info.total_params_len); - var param_index: usize = 0; + var runtime_param_index: usize = 0; + var total_param_index: usize = 0; for (fn_info.param_body) |inst| { const name = switch (zir_tags[inst]) { .param, .param_comptime => blk: { @@ -3686,16 +3716,16 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { else => continue, }; if (func.comptime_args) |comptime_args| { - const arg_tv = comptime_args[param_index]; + const arg_tv = comptime_args[total_param_index]; if (arg_tv.val.tag() != .unreachable_value) { // We have a comptime value for this parameter. const arg = try sema.addConstant(arg_tv.ty, arg_tv.val); sema.inst_map.putAssumeCapacityNoClobber(inst, arg); - param_index += 1; + total_param_index += 1; continue; } } - const param_type = fn_ty.fnParamType(param_index); + const param_type = fn_ty.fnParamType(runtime_param_index); const ty_ref = try sema.addType(param_type); const arg_index = @intCast(u32, sema.air_instructions.len); inner_block.instructions.appendAssumeCapacity(arg_index); @@ -3707,7 +3737,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { } }, }); sema.inst_map.putAssumeCapacityNoClobber(inst, Air.indexToRef(arg_index)); - param_index += 1; + total_param_index += 1; + runtime_param_index += 1; } func.state = .in_progress; @@ -3715,6 +3746,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { _ = sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) { error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, else => |e| return e, }; diff --git a/src/Sema.zig b/src/Sema.zig index 9a7eb6fc40..8bcdcb63c9 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -37,13 +37,15 @@ branch_count: u32 = 0, /// contain a mapped source location. src: LazySrcLoc = .{ .token_offset = 0 }, decl_val_table: std.AutoHashMapUnmanaged(*Decl, Air.Inst.Ref) = .{}, -/// `param` instructions are collected here to be used by the `func` instruction. -params: std.ArrayListUnmanaged(Param) = .{}, -/// When doing a generic function instantiation, this array collects a `Value` object for -/// each parameter that is comptime known and thus elided from the generated function. -/// This memory is allocated by a parent `Sema` and owned by the values arena of the owner_decl. +/// When doing a generic function instantiation, this array collects a +/// `Value` object for each parameter that is comptime known and thus elided +/// from the generated function. This memory is allocated by a parent `Sema` and +/// owned by the values arena of the Sema owner_decl. comptime_args: []TypedValue = &.{}, -next_arg_index: usize = 0, +/// Marks the function instruction that `comptime_args` applies to so that we +/// don't accidentally apply it to a function prototype which is used in the +/// type expression of a generic function parameter. +comptime_args_fn_inst: Zir.Inst.Index = 0, const std = @import("std"); const mem = std.mem; @@ -67,13 +69,6 @@ const LazySrcLoc = Module.LazySrcLoc; const RangeSet = @import("RangeSet.zig"); const target_util = @import("target.zig"); -const Param = struct { - name: [:0]const u8, - /// `noreturn` means `anytype`. - ty: Type, - is_comptime: bool, -}; - pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref); pub fn deinit(sema: *Sema) void { @@ -83,7 +78,6 @@ pub fn deinit(sema: *Sema) void { sema.air_values.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); - sema.params.deinit(gpa); sema.* = undefined; } @@ -466,6 +460,26 @@ pub fn analyzeBody( i += 1; continue; }, + .param => { + try sema.zirParam(block, inst, false); + i += 1; + continue; + }, + .param_comptime => { + try sema.zirParam(block, inst, true); + i += 1; + continue; + }, + .param_anytype => { + try sema.zirParamAnytype(block, inst, false); + i += 1; + continue; + }, + .param_anytype_comptime => { + try sema.zirParamAnytype(block, inst, true); + i += 1; + continue; + }, // Special case instructions to handle comptime control flow. .repeat_inline => { @@ -504,88 +518,6 @@ pub fn analyzeBody( return break_inst; } }, - .param => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; - const src = inst_data.src(); - const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; - const param_name = sema.code.nullTerminatedString(extra.name); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - const param_ty = try sema.resolveType(block, src, extra.ty); - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = param_ty, - .is_comptime = false, - }); - break :blk try sema.addConstUndef(param_ty); - }, - .param_comptime => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; - const src = inst_data.src(); - const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; - const param_name = sema.code.nullTerminatedString(extra.name); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - const param_ty = try sema.resolveType(block, src, extra.ty); - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = param_ty, - .is_comptime = true, - }); - break :blk try sema.addConstUndef(param_ty); - }, - .param_anytype => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].str_tok; - const param_name = inst_data.get(sema.code); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = Type.initTag(.noreturn), - .is_comptime = false, - }); - break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); - }, - .param_anytype_comptime => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].str_tok; - const param_name = inst_data.get(sema.code); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = Type.initTag(.noreturn), - .is_comptime = true, - }); - break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); - }, }; if (sema.typeOf(air_inst).isNoReturn()) return always_noreturn; @@ -697,6 +629,7 @@ fn resolveValue( air_ref: Air.Inst.Ref, ) CompileError!Value { if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| { + if (val.tag() == .generic_poison) return error.GenericPoison; return val; } return sema.failWithNeededComptime(block, src); @@ -714,6 +647,7 @@ fn resolveConstValue( switch (val.tag()) { .undef => return sema.failWithUseOfUndef(block, src), .variable => return sema.failWithNeededComptime(block, src), + .generic_poison => return error.GenericPoison, else => return val, } } @@ -2422,7 +2356,7 @@ fn analyzeCall( call_src: LazySrcLoc, modifier: std.builtin.CallOptions.Modifier, ensure_result_used: bool, - args: []const Air.Inst.Ref, + uncasted_args: []const Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const mod = sema.mod; @@ -2444,22 +2378,22 @@ fn analyzeCall( const fn_params_len = func_ty_info.param_types.len; if (func_ty_info.is_var_args) { assert(cc == .C); - if (args.len < fn_params_len) { + if (uncasted_args.len < fn_params_len) { // TODO add error note: declared here return mod.fail( &block.base, func_src, "expected at least {d} argument(s), found {d}", - .{ fn_params_len, args.len }, + .{ fn_params_len, uncasted_args.len }, ); } - } else if (fn_params_len != args.len) { + } else if (fn_params_len != uncasted_args.len) { // TODO add error note: declared here return mod.fail( &block.base, func_src, "expected {d} argument(s), found {d}", - .{ fn_params_len, args.len }, + .{ fn_params_len, uncasted_args.len }, ); } @@ -2485,6 +2419,14 @@ fn analyzeCall( const is_inline_call = is_comptime_call or modifier == .always_inline or func_ty_info.cc == .Inline; const result: Air.Inst.Ref = if (is_inline_call) res: { + // TODO look into not allocating this args array + const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); + for (uncasted_args) |uncasted_arg, i| { + const param_ty = func_ty.fnParamType(i); + const arg_src = call_src; // TODO: better source location + args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src); + } + const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = switch (func_val.tag()) { .function => func_val.castTag(.function).?.data, @@ -2574,13 +2516,12 @@ fn analyzeCall( const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = func_val.castTag(.function).?.data; // Check the Module's generic function map with an adapted context, so that we - // can match against `args` rather than doing the work below to create a generic Scope - // only to junk it if it matches an existing instantiation. + // can match against `uncasted_args` rather than doing the work below to create a + // generic Scope only to junk it if it matches an existing instantiation. // TODO const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); const zir_tags = sema.code.instructions.items(.tag); - var non_comptime_args_len: u32 = 0; const new_func = new_func: { const namespace = module_fn.owner_decl.namespace; try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); @@ -2622,7 +2563,8 @@ fn analyzeCall( .namespace = namespace, .func = null, .owner_func = null, - .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, args.len), + .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), + .comptime_args_fn_inst = module_fn.zir_body_inst, }; defer child_sema.deinit(); @@ -2634,41 +2576,59 @@ fn analyzeCall( .inlining = null, .is_comptime = true, }; - defer child_block.instructions.deinit(gpa); + defer { + child_block.instructions.deinit(gpa); + child_block.params.deinit(gpa); + } - try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, args.len)); + try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); var arg_i: usize = 0; for (fn_info.param_body) |inst| { const is_comptime = switch (zir_tags[inst]) { .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => false, // TODO make true for always comptime types + .param, .param_anytype => false, else => continue, }; - if (is_comptime) { - // TODO: pass .unneeded to resolveConstValue and then if we get - // error.NeededSourceLocation resolve the arg source location and - // try again. - const arg_src = call_src; - const arg = args[arg_i]; - const arg_val = try sema.resolveConstValue(block, arg_src, arg); - child_sema.comptime_args[arg_i] = .{ - .ty = try sema.typeOf(arg).copy(&new_decl_arena.allocator), - .val = try arg_val.copy(&new_decl_arena.allocator), - }; + // TODO: pass .unneeded to resolveConstValue and then if we get + // error.NeededSourceLocation resolve the arg source location and + // try again. + const arg_src = call_src; + const arg = uncasted_args[arg_i]; + if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else { - non_comptime_args_len += 1; + } else if (is_comptime) { + return sema.failWithNeededComptime(block, arg_src); + } + arg_i += 1; + } + const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); + const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); + const new_func = new_func_val.castTag(.function).?.data; + + arg_i = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, + } + const arg = child_sema.inst_map.get(inst).?; + const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; + + if (arg_val.tag() == .generic_poison) { child_sema.comptime_args[arg_i] = .{ .ty = Type.initTag(.noreturn), .val = Value.initTag(.unreachable_value), }; + } else { + child_sema.comptime_args[arg_i] = .{ + .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), + .val = try arg_val.copy(&new_decl_arena.allocator), + }; } + arg_i += 1; } - const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); - const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); - const new_func = new_func_val.castTag(.function).?.data; // Populate the Decl ty/val with the function and its type. new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); @@ -2690,31 +2650,72 @@ fn analyzeCall( // Make a runtime call to the new function, making sure to omit the comptime args. try sema.requireRuntimeBlock(block, call_src); + const new_func_val = sema.resolveConstValue(block, .unneeded, new_func) catch unreachable; + const new_module_func = new_func_val.castTag(.function).?.data; + const comptime_args = new_module_func.comptime_args.?; + const runtime_args_len = count: { + var count: u32 = 0; + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => { + if (comptime_args[arg_i].val.tag() == .unreachable_value) { + count += 1; + } + arg_i += 1; + }, + else => continue, + } + } + break :count count; + }; + const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); + { + const new_fn_ty = new_module_func.owner_decl.ty; + var runtime_i: u32 = 0; + var total_i: u32 = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, + } + const is_runtime = comptime_args[total_i].val.tag() == .unreachable_value; + if (is_runtime) { + const param_ty = new_fn_ty.fnParamType(runtime_i); + const arg_src = call_src; // TODO: better source location + const uncasted_arg = uncasted_args[total_i]; + const casted_arg = try sema.coerce(block, param_ty, uncasted_arg, arg_src); + runtime_args[runtime_i] = casted_arg; + runtime_i += 1; + } + total_i += 1; + } + } try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + - non_comptime_args_len); + runtime_args_len); const func_inst = try block.addInst(.{ .tag = .call, .data = .{ .pl_op = .{ .operand = new_func, .payload = sema.addExtraAssumeCapacity(Air.Call{ - .args_len = non_comptime_args_len, + .args_len = runtime_args_len, }), } }, }); - var arg_i: usize = 0; - for (fn_info.param_body) |inst| { - const is_comptime = switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => false, // TODO make true for always comptime types - else => continue, - }; - if (is_comptime) { - sema.air_extra.appendAssumeCapacity(@enumToInt(args[arg_i])); - } - arg_i += 1; - } + sema.appendRefsAssumeCapacity(runtime_args); break :res func_inst; } else res: { + const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); + for (uncasted_args) |uncasted_arg, i| { + if (i < fn_params_len) { + const param_ty = func_ty.fnParamType(i); + const arg_src = call_src; // TODO: better source location + args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src); + } else { + args[i] = uncasted_arg; + } + } + try sema.requireRuntimeBlock(block, call_src); try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + args.len); @@ -3416,7 +3417,7 @@ fn funcCommon( const fn_ty: Type = fn_ty: { // Hot path for some common function types. - if (sema.params.items.len == 0 and !var_args and align_val.tag() == .null_value and + if (block.params.items.len == 0 and !var_args and align_val.tag() == .null_value and !inferred_error_set) { if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) { @@ -3436,19 +3437,15 @@ fn funcCommon( } } - var any_are_comptime = false; - const param_types = try sema.arena.alloc(Type, sema.params.items.len); - const comptime_params = try sema.arena.alloc(bool, sema.params.items.len); - for (sema.params.items) |param, i| { - if (param.ty.tag() == .noreturn) { - param_types[i] = Type.initTag(.noreturn); // indicates anytype - } else { - param_types[i] = param.ty; - } + var is_generic = false; + const param_types = try sema.arena.alloc(Type, block.params.items.len); + const comptime_params = try sema.arena.alloc(bool, block.params.items.len); + for (block.params.items) |param, i| { + param_types[i] = param.ty; comptime_params[i] = param.is_comptime; - any_are_comptime = any_are_comptime or param.is_comptime; + is_generic = is_generic or param.is_comptime or + param.ty.tag() == .generic_poison or param.ty.requiresComptime(); } - sema.params.clearRetainingCapacity(); if (align_val.tag() != .null_value) { return mod.fail(&block.base, src, "TODO implement support for function prototypes to have alignment specified", .{}); @@ -3471,7 +3468,7 @@ fn funcCommon( .return_type = return_type, .cc = cc, .is_var_args = var_args, - .is_generic = any_are_comptime, + .is_generic = is_generic, }); }; @@ -3530,12 +3527,16 @@ fn funcCommon( const is_inline = fn_ty.fnCallingConvention() == .Inline; const anal_state: Module.Fn.Analysis = if (is_inline) .inline_only else .queued; + const comptime_args: ?[*]TypedValue = if (sema.comptime_args_fn_inst == body_inst) blk: { + break :blk if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr; + } else null; + const fn_payload = try sema.arena.create(Value.Payload.Function); new_func.* = .{ .state = anal_state, .zir_body_inst = body_inst, .owner_decl = sema.owner_decl, - .comptime_args = if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr, + .comptime_args = comptime_args, .lbrace_line = src_locs.lbrace_line, .rbrace_line = src_locs.rbrace_line, .lbrace_column = @truncate(u16, src_locs.columns), @@ -3548,6 +3549,113 @@ fn funcCommon( return sema.addConstant(fn_ty, Value.initPayload(&fn_payload.base)); } +fn zirParam( + sema: *Sema, + block: *Scope.Block, + inst: Zir.Inst.Index, + is_comptime: bool, +) CompileError!void { + const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; + const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index); + const param_name = sema.code.nullTerminatedString(extra.data.name); + const body = sema.code.extra[extra.end..][0..extra.data.body_len]; + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + _ = param_name; + + // We could be in a generic function instantiation, or we could be evaluating a generic + // function without any comptime args provided. + const param_ty = param_ty: { + const err = err: { + // Make sure any nested param instructions don't clobber our work. + const prev_params = block.params; + block.params = .{}; + defer { + block.params.deinit(sema.gpa); + block.params = prev_params; + } + + if (sema.resolveBody(block, body)) |param_ty_inst| { + if (sema.analyzeAsType(block, src, param_ty_inst)) |param_ty| { + break :param_ty param_ty; + } else |err| break :err err; + } else |err| break :err err; + }; + switch (err) { + error.GenericPoison => { + // The type is not available until the generic instantiation. + // We result the param instruction with a poison value and + // insert an anytype parameter. + try block.params.append(sema.gpa, .{ + .ty = Type.initTag(.generic_poison), + .is_comptime = is_comptime, + }); + try sema.inst_map.putNoClobber(sema.gpa, inst, .generic_poison); + return; + }, + else => |e| return e, + } + }; + if (sema.inst_map.get(inst)) |arg| { + if (is_comptime or param_ty.requiresComptime()) { + // We have a comptime value for this parameter so it should be elided from the + // function type of the function instruction in this block. + const coerced_arg = try sema.coerce(block, param_ty, arg, src); + sema.inst_map.putAssumeCapacity(inst, coerced_arg); + return; + } + // Even though a comptime argument is provided, the generic function wants to treat + // this as a runtime parameter. + assert(sema.inst_map.remove(inst)); + } + + try block.params.append(sema.gpa, .{ + .ty = param_ty, + .is_comptime = is_comptime, + }); + const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); + try sema.inst_map.putNoClobber(sema.gpa, inst, result); +} + +fn zirParamAnytype( + sema: *Sema, + block: *Scope.Block, + inst: Zir.Inst.Index, + is_comptime: bool, +) CompileError!void { + const inst_data = sema.code.instructions.items(.data)[inst].str_tok; + const param_name = inst_data.get(sema.code); + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + _ = param_name; + + if (sema.inst_map.get(inst)) |air_ref| { + const param_ty = sema.typeOf(air_ref); + if (is_comptime or param_ty.requiresComptime()) { + // We have a comptime value for this parameter so it should be elided from the + // function type of the function instruction in this block. + return; + } + // The map is already populated but we do need to add a runtime parameter. + try block.params.append(sema.gpa, .{ + .ty = param_ty, + .is_comptime = false, + }); + return; + } + + // We are evaluating a generic function without any comptime args provided. + + try block.params.append(sema.gpa, .{ + .ty = Type.initTag(.generic_poison), + .is_comptime = is_comptime, + }); + try sema.inst_map.put(sema.gpa, inst, .generic_poison); +} + fn zirAs(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -7618,8 +7726,10 @@ fn coerce( inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - if (dest_type_unresolved.tag() == .var_args_param) { - return sema.coerceVarArgParam(block, inst, inst_src); + switch (dest_type_unresolved.tag()) { + .var_args_param => return sema.coerceVarArgParam(block, inst, inst_src), + .generic_poison => return inst, + else => {}, } const dest_type_src = inst_src; // TODO better source location const dest_type = try sema.resolveTypeFields(block, dest_type_src, dest_type_unresolved); @@ -8820,6 +8930,7 @@ fn typeHasOnePossibleValue( .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, + .generic_poison => unreachable, }; } @@ -8942,6 +9053,8 @@ pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref { .fn_ccc_void_no_args => return .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int => return .single_const_pointer_to_comptime_int_type, .const_slice_u8 => return .const_slice_u8_type, + .anyerror_void_error_union => return .anyerror_void_error_union_type, + .generic_poison => return .generic_poison_type, else => {}, } try sema.air_instructions.append(sema.gpa, .{ @@ -9015,10 +9128,3 @@ fn isComptimeKnown( ) !bool { return (try sema.resolveMaybeUndefVal(block, src, inst)) != null; } - -fn nextArgIsComptimeElided(sema: *Sema) bool { - if (sema.comptime_args.len == 0) return false; - const result = sema.comptime_args[sema.next_arg_index].val.tag() != .unreachable_value; - sema.next_arg_index += 1; - return result; -} diff --git a/src/Zir.zig b/src/Zir.zig index 0b93208564..b4cbd9c875 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -1704,6 +1704,8 @@ pub const Inst = struct { fn_ccc_void_no_args_type, single_const_pointer_to_comptime_int_type, const_slice_u8_type, + anyerror_void_error_union_type, + generic_poison_type, /// `undefined` (untyped) undef, @@ -1731,6 +1733,9 @@ pub const Inst = struct { calling_convention_c, /// `std.builtin.CallingConvention.Inline` calling_convention_inline, + /// Used for generic parameters where the type and value + /// is not known until generic function instantiation. + generic_poison, _, @@ -1909,6 +1914,14 @@ pub const Inst = struct { .ty = Type.initTag(.type), .val = Value.initTag(.const_slice_u8_type), }, + .anyerror_void_error_union_type = .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.anyerror_void_error_union_type), + }, + .generic_poison_type = .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.generic_poison_type), + }, .enum_literal_type = .{ .ty = Type.initTag(.type), .val = Value.initTag(.enum_literal_type), @@ -2006,6 +2019,10 @@ pub const Inst = struct { .ty = Type.initTag(.calling_convention), .val = .{ .ptr_otherwise = &calling_convention_inline_payload.base }, }, + .generic_poison = .{ + .ty = Type.initTag(.generic_poison), + .val = Value.initTag(.generic_poison), + }, }); }; @@ -2787,10 +2804,12 @@ pub const Inst = struct { args: Ref, }; + /// Trailing: inst: Index // for every body_len pub const Param = struct { /// Null-terminated string index. name: u32, - ty: Ref, + /// The body contains the type of the parameter. + body_len: u32, }; /// Trailing: @@ -3348,11 +3367,16 @@ const Writer = struct { fn writeParam(self: *Writer, stream: anytype, inst: Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_tok; - const extra = self.code.extraData(Inst.Param, inst_data.payload_index).data; + const extra = self.code.extraData(Inst.Param, inst_data.payload_index); + const body = self.code.extra[extra.end..][0..extra.data.body_len]; try stream.print("\"{}\", ", .{ - std.zig.fmtEscapes(self.code.nullTerminatedString(extra.name)), + std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)), }); - try self.writeInstRef(stream, extra.ty); + try stream.writeAll("{\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); try stream.writeAll(") "); try self.writeSrc(stream, inst_data.src()); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 8b3edcfe23..4a589ea66d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -839,6 +839,10 @@ pub const DeclGen = struct { .False, ); }, + .ComptimeInt => unreachable, + .ComptimeFloat => unreachable, + .Type => unreachable, + .EnumLiteral => unreachable, else => return self.todo("implement const of type '{}'", .{tv.ty}), } } diff --git a/src/type.zig b/src/type.zig index 237614e372..0e99a929f0 100644 --- a/src/type.zig +++ b/src/type.zig @@ -130,6 +130,7 @@ pub const Type = extern union { => return .Union, .var_args_param => unreachable, // can be any type + .generic_poison => unreachable, // must be handled earlier } } @@ -699,6 +700,7 @@ pub const Type = extern union { .export_options, .extern_options, .@"anyframe", + .generic_poison, => unreachable, .array_u8, @@ -1083,11 +1085,117 @@ pub const Type = extern union { }, .inferred_alloc_const => return writer.writeAll("(inferred_alloc_const)"), .inferred_alloc_mut => return writer.writeAll("(inferred_alloc_mut)"), + .generic_poison => return writer.writeAll("(generic poison)"), } unreachable; } } + /// Anything that reports hasCodeGenBits() false returns false here as well. + pub fn requiresComptime(ty: Type) bool { + return switch (ty.tag()) { + .u1, + .u8, + .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, + .u128, + .i128, + .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, + .anyerror, + .noreturn, + .@"anyframe", + .@"null", + .@"undefined", + .atomic_ordering, + .atomic_rmw_op, + .calling_convention, + .float_mode, + .reduce_op, + .call_options, + .export_options, + .extern_options, + .manyptr_u8, + .manyptr_const_u8, + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + .single_const_pointer_to_comptime_int, + .const_slice_u8, + .anyerror_void_error_union, + .empty_struct_literal, + .function, + .empty_struct, + .error_set, + .error_set_single, + .error_set_inferred, + .@"opaque", + => false, + + .type, + .comptime_int, + .comptime_float, + .enum_literal, + => true, + + .var_args_param => unreachable, + .inferred_alloc_mut => unreachable, + .inferred_alloc_const => unreachable, + .generic_poison => unreachable, + + .array_u8, + .array_u8_sentinel_0, + .array, + .array_sentinel, + .vector, + .pointer, + .single_const_pointer, + .single_mut_pointer, + .many_const_pointer, + .many_mut_pointer, + .c_const_pointer, + .c_mut_pointer, + .const_slice, + .mut_slice, + .int_signed, + .int_unsigned, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, + .error_union, + .anyframe_T, + .@"struct", + .@"union", + .union_tagged, + .enum_simple, + .enum_full, + .enum_nonexhaustive, + => false, // TODO some of these should be `true` depending on their child types + }; + } + pub fn toValue(self: Type, allocator: *Allocator) Allocator.Error!Value { switch (self.tag()) { .u1 => return Value.initTag(.u1_type), @@ -1287,6 +1395,7 @@ pub const Type = extern union { .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, .var_args_param => unreachable, + .generic_poison => unreachable, }; } @@ -1509,6 +1618,8 @@ pub const Type = extern union { .@"opaque", .var_args_param, => unreachable, + + .generic_poison => unreachable, }; } @@ -1536,6 +1647,7 @@ pub const Type = extern union { .inferred_alloc_mut => unreachable, .@"opaque" => unreachable, .var_args_param => unreachable, + .generic_poison => unreachable, .@"struct" => { const s = self.castTag(.@"struct").?.data; @@ -1702,6 +1814,7 @@ pub const Type = extern union { .inferred_alloc_mut => unreachable, .@"opaque" => unreachable, .var_args_param => unreachable, + .generic_poison => unreachable, .@"struct" => { @panic("TODO bitSize struct"); @@ -2626,6 +2739,7 @@ pub const Type = extern union { .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, + .generic_poison => unreachable, }; } @@ -3039,6 +3153,7 @@ pub const Type = extern union { single_const_pointer_to_comptime_int, const_slice_u8, anyerror_void_error_union, + generic_poison, /// This is a special type for variadic parameters of a function call. /// Casts to it will validate that the type can be passed to a c calling convetion function. var_args_param, @@ -3136,6 +3251,7 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int, .anyerror_void_error_union, .const_slice_u8, + .generic_poison, .inferred_alloc_const, .inferred_alloc_mut, .var_args_param, diff --git a/src/value.zig b/src/value.zig index 134b51e494..bd1cc57416 100644 --- a/src/value.zig +++ b/src/value.zig @@ -76,6 +76,8 @@ pub const Value = extern union { fn_ccc_void_no_args_type, single_const_pointer_to_comptime_int_type, const_slice_u8_type, + anyerror_void_error_union_type, + generic_poison_type, undef, zero, @@ -85,6 +87,7 @@ pub const Value = extern union { null_value, bool_true, bool_false, + generic_poison, abi_align_default, empty_struct_value, @@ -188,6 +191,8 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .anyframe_type, .const_slice_u8_type, + .anyerror_void_error_union_type, + .generic_poison_type, .enum_literal_type, .undef, .zero, @@ -210,6 +215,7 @@ pub const Value = extern union { .call_options_type, .export_options_type, .extern_options_type, + .generic_poison, => @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"), .int_big_positive, @@ -366,6 +372,8 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .anyframe_type, .const_slice_u8_type, + .anyerror_void_error_union_type, + .generic_poison_type, .enum_literal_type, .undef, .zero, @@ -388,6 +396,7 @@ pub const Value = extern union { .call_options_type, .export_options_type, .extern_options_type, + .generic_poison, => unreachable, .ty => { @@ -556,6 +565,9 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"), .anyframe_type => return out_stream.writeAll("anyframe"), .const_slice_u8_type => return out_stream.writeAll("[]const u8"), + .anyerror_void_error_union_type => return out_stream.writeAll("anyerror!void"), + .generic_poison_type => return out_stream.writeAll("(generic poison type)"), + .generic_poison => return out_stream.writeAll("(generic poison)"), .enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"), .manyptr_u8_type => return out_stream.writeAll("[*]u8"), .manyptr_const_u8_type => return out_stream.writeAll("[*]const u8"), @@ -709,6 +721,8 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int), .anyframe_type => Type.initTag(.@"anyframe"), .const_slice_u8_type => Type.initTag(.const_slice_u8), + .anyerror_void_error_union_type => Type.initTag(.anyerror_void_error_union), + .generic_poison_type => Type.initTag(.generic_poison), .enum_literal_type => Type.initTag(.enum_literal), .manyptr_u8_type => Type.initTag(.manyptr_u8), .manyptr_const_u8_type => Type.initTag(.manyptr_const_u8), @@ -732,46 +746,7 @@ pub const Value = extern union { return Type.initPayload(&buffer.base); }, - .undef, - .zero, - .one, - .void_value, - .unreachable_value, - .empty_array, - .bool_true, - .bool_false, - .null_value, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .function, - .extern_fn, - .variable, - .decl_ref, - .decl_ref_mut, - .elem_ptr, - .field_ptr, - .bytes, - .repeated, - .array, - .slice, - .float_16, - .float_32, - .float_64, - .float_128, - .enum_literal, - .enum_field_index, - .@"error", - .error_union, - .empty_struct_value, - .@"struct", - .@"union", - .inferred_alloc, - .inferred_alloc_comptime, - .abi_align_default, - .eu_payload_ptr, - => unreachable, + else => unreachable, }; } diff --git a/test/cases.zig b/test/cases.zig index 840ee7a4ac..a342b91a6f 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1572,7 +1572,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ const x = asm volatile ("syscall" \\ : [o] "{rax}" (-> number) \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) + \\ [arg1] "{rdi}" (60) \\ : "rcx", "r11", "memory" \\ ); \\ _ = x; -- cgit v1.2.3 From f58cbef1659742e57377d3f8c92a0b9b97af91ad Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Aug 2021 23:02:13 -0700 Subject: stage2: std.mem.eql works now * The `indexable_ptr_len` ZIR instruction now uses a `none_or_ref` ResultLoc. This prevents an unnecessary `ref` instruction from being emitted. * Sema: Fix `analyzeCall` using the incorrect ZIR object for the generic function callee. * LLVM backend: `genTypedValue` supports a `Slice` type encoded with the `decl_ref` `Value`. --- src/AstGen.zig | 2 +- src/Sema.zig | 73 +++++++++++++++++++++++++++---------------------- src/codegen/llvm.zig | 30 ++++++++++++++++---- test/behavior/basic.zig | 9 ++++++ test/behavior/misc.zig | 8 ------ 5 files changed, 75 insertions(+), 47 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/AstGen.zig b/src/AstGen.zig index 493e0a75f4..9886e4d809 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -5423,7 +5423,7 @@ fn forExpr( const tree = astgen.tree; const token_tags = tree.tokens.items(.tag); - const array_ptr = try expr(parent_gz, scope, .ref, for_full.ast.cond_expr); + const array_ptr = try expr(parent_gz, scope, .none_or_ref, for_full.ast.cond_expr); const len = try parent_gz.addUnNode(.indexable_ptr_len, array_ptr, for_full.ast.cond_expr); const index_ptr = blk: { diff --git a/src/Sema.zig b/src/Sema.zig index 8bcdcb63c9..dff11301db 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1306,38 +1306,44 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const array_ptr = sema.resolveInst(inst_data.operand); - const array_ptr_src = src; + const array = sema.resolveInst(inst_data.operand); + const array_ty = sema.typeOf(array); - const elem_ty = sema.typeOf(array_ptr).elemType(); - if (elem_ty.isSlice()) { - const slice_inst = try sema.analyzeLoad(block, src, array_ptr, array_ptr_src); - return sema.analyzeSliceLen(block, src, slice_inst); + if (array_ty.isSlice()) { + return sema.analyzeSliceLen(block, src, array); } - if (!elem_ty.isIndexable()) { - const cond_src: LazySrcLoc = .{ .node_offset_for_cond = inst_data.src_node }; - const msg = msg: { - const msg = try sema.mod.errMsg( - &block.base, - cond_src, - "type '{}' does not support indexing", - .{elem_ty}, - ); - errdefer msg.destroy(sema.gpa); - try sema.mod.errNote( - &block.base, - cond_src, - msg, - "for loop operand must be an array, slice, tuple, or vector", - .{}, - ); - break :msg msg; - }; - return sema.mod.failWithOwnedErrorMsg(&block.base, msg); + + if (array_ty.isSinglePointer()) { + const elem_ty = array_ty.elemType(); + if (elem_ty.isSlice()) { + const slice_inst = try sema.analyzeLoad(block, src, array, src); + return sema.analyzeSliceLen(block, src, slice_inst); + } + if (!elem_ty.isIndexable()) { + const msg = msg: { + const msg = try sema.mod.errMsg( + &block.base, + src, + "type '{}' does not support indexing", + .{elem_ty}, + ); + errdefer msg.destroy(sema.gpa); + try sema.mod.errNote( + &block.base, + src, + msg, + "for loop operand must be an array, slice, tuple, or vector", + .{}, + ); + break :msg msg; + }; + return sema.mod.failWithOwnedErrorMsg(&block.base, msg); + } + const result_ptr = try sema.fieldPtr(block, src, array, "len", src); + return sema.analyzeLoad(block, src, result_ptr, src); } - const result_ptr = try sema.fieldPtr(block, src, array_ptr, "len", src); - const result_ptr_src = array_ptr_src; - return sema.analyzeLoad(block, src, result_ptr, result_ptr_src); + + return sema.mod.fail(&block.base, src, "TODO implement Sema.zirIndexablePtrLen", .{}); } fn zirAllocExtended( @@ -2520,10 +2526,11 @@ fn analyzeCall( // generic Scope only to junk it if it matches an existing instantiation. // TODO - const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); - const zir_tags = sema.code.instructions.items(.tag); + const namespace = module_fn.owner_decl.namespace; + const fn_zir = namespace.file_scope.zir; + const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst); + const zir_tags = fn_zir.instructions.items(.tag); const new_func = new_func: { - const namespace = module_fn.owner_decl.namespace; try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); // Create a Decl for the new function. @@ -2558,7 +2565,7 @@ fn analyzeCall( .mod = mod, .gpa = gpa, .arena = sema.arena, - .code = sema.code, + .code = fn_zir, .owner_decl = new_decl, .namespace = namespace, .func = null, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4a589ea66d..0e73469687 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -701,11 +701,31 @@ pub const DeclGen = struct { }, .Pointer => switch (tv.val.tag()) { .decl_ref => { - const decl = tv.val.castTag(.decl_ref).?.data; - decl.alive = true; - const val = try self.resolveGlobalDecl(decl); - const llvm_type = try self.llvmType(tv.ty); - return val.constBitCast(llvm_type); + if (tv.ty.isSlice()) { + var buf: Type.Payload.ElemType = undefined; + const ptr_ty = tv.ty.slicePtrFieldType(&buf); + var slice_len: Value.Payload.U64 = .{ + .base = .{ .tag = .int_u64 }, + .data = tv.val.sliceLen(), + }; + const fields: [2]*const llvm.Value = .{ + try self.genTypedValue(.{ + .ty = ptr_ty, + .val = tv.val, + }), + try self.genTypedValue(.{ + .ty = Type.initTag(.usize), + .val = Value.initPayload(&slice_len.base), + }), + }; + return self.context.constStruct(&fields, fields.len, .False); + } else { + const decl = tv.val.castTag(.decl_ref).?.data; + decl.alive = true; + const val = try self.resolveGlobalDecl(decl); + const llvm_type = try self.llvmType(tv.ty); + return val.constBitCast(llvm_type); + } }, .variable => { const decl = tv.val.castTag(.variable).?.data.owner_decl; diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index c5c2972ffc..ac1dc3889c 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const mem = std.mem; const expect = std.testing.expect; // normal comment @@ -83,3 +84,11 @@ test "unicode escape in character literal" { test "unicode character in character literal" { try expect('💩' == 128169); } + +fn first4KeysOfHomeRow() []const u8 { + return "aoeu"; +} + +test "return string from function" { + try expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu")); +} diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig index ddb90773d8..8a0761dfd8 100644 --- a/test/behavior/misc.zig +++ b/test/behavior/misc.zig @@ -5,14 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings; const mem = std.mem; const builtin = @import("builtin"); -fn first4KeysOfHomeRow() []const u8 { - return "aoeu"; -} - -test "return string from function" { - try expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu")); -} - test "memcpy and memset intrinsics" { var foo: [20]u8 = undefined; var bar: [20]u8 = undefined; -- cgit v1.2.3 From c03a04a58942446b48e9294df991a17a3a6f7b48 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Aug 2021 19:15:59 -0700 Subject: stage2: return type expressions of generic functions * ZIR encoding for function instructions have a body for the return type. This lets Sema for generic functions do the same thing it does for parameters, handling `error.GenericPoison` in the evaluation of the return type by marking the function as generic. * Sema: fix missing block around the new Decl arena finalization. This led to a memory corruption. * Added some floating point support to the LLVM backend but didn't get far enough to pass any new tests. --- src/AstGen.zig | 70 +++++----- src/Module.zig | 3 + src/Sema.zig | 284 ++++++++++++++++++++++---------------- src/Zir.zig | 64 +++++++-- src/codegen/llvm.zig | 16 ++- src/codegen/llvm/bindings.zig | 18 +++ src/print_air.zig | 2 +- test/behavior/generics.zig | 56 ++++++++ test/behavior/generics_stage1.zig | 39 +----- 9 files changed, 344 insertions(+), 208 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/AstGen.zig b/src/AstGen.zig index 9886e4d809..febe581e35 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1041,6 +1041,7 @@ fn fnProtoExpr( fn_proto: ast.full.FnProto, ) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; + const gpa = astgen.gpa; const tree = astgen.tree; const token_tags = tree.tokens.items(.tag); @@ -1083,7 +1084,6 @@ fn fnProtoExpr( .param_anytype; _ = try gz.addStrTok(tag, param_name, name_token); } else { - const gpa = astgen.gpa; const param_type_node = param.type_expr; assert(param_type_node != 0); var param_gz = gz.makeSubBlock(scope); @@ -1113,15 +1113,13 @@ fn fnProtoExpr( if (is_inferred_error) { return astgen.failTok(maybe_bang, "function prototype may not have inferred error set", .{}); } - const return_type_inst = try AstGen.expr( - gz, - scope, - .{ .ty = .type_type }, - fn_proto.ast.return_type, - ); + var ret_gz = gz.makeSubBlock(scope); + defer ret_gz.instructions.deinit(gpa); + const ret_ty = try expr(&ret_gz, scope, coerced_type_rl, fn_proto.ast.return_type); + const ret_br = try ret_gz.addBreak(.break_inline, 0, ret_ty); const cc: Zir.Inst.Ref = if (fn_proto.ast.callconv_expr != 0) - try AstGen.expr( + try expr( gz, scope, .{ .ty = .calling_convention_type }, @@ -1133,7 +1131,8 @@ fn fnProtoExpr( const result = try gz.addFunc(.{ .src_node = fn_proto.ast.proto_node, .param_block = 0, - .ret_ty = return_type_inst, + .ret_ty = ret_gz.instructions.items, + .ret_br = ret_br, .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = align_inst, @@ -3005,12 +3004,10 @@ fn fnDecl( break :inst try comptimeExpr(&decl_gz, params_scope, .{ .ty = .const_slice_u8_type }, fn_proto.ast.section_expr); }; - const return_type_inst = try AstGen.expr( - &decl_gz, - params_scope, - .{ .ty = .type_type }, - fn_proto.ast.return_type, - ); + var ret_gz = gz.makeSubBlock(params_scope); + defer ret_gz.instructions.deinit(gpa); + const ret_ty = try expr(&decl_gz, params_scope, coerced_type_rl, fn_proto.ast.return_type); + const ret_br = try ret_gz.addBreak(.break_inline, 0, ret_ty); const cc: Zir.Inst.Ref = blk: { if (fn_proto.ast.callconv_expr != 0) { @@ -3021,7 +3018,7 @@ fn fnDecl( .{}, ); } - break :blk try AstGen.expr( + break :blk try expr( &decl_gz, params_scope, .{ .ty = .calling_convention_type }, @@ -3046,7 +3043,8 @@ fn fnDecl( } break :func try decl_gz.addFunc(.{ .src_node = decl_node, - .ret_ty = return_type_inst, + .ret_ty = ret_gz.instructions.items, + .ret_br = ret_br, .param_block = block_inst, .body = &[0]Zir.Inst.Index{}, .cc = cc, @@ -3085,7 +3083,8 @@ fn fnDecl( break :func try decl_gz.addFunc(.{ .src_node = decl_node, .param_block = block_inst, - .ret_ty = return_type_inst, + .ret_ty = ret_gz.instructions.items, + .ret_br = ret_br, .body = fn_gz.instructions.items, .cc = cc, .align_inst = .none, // passed in the per-decl data @@ -3430,7 +3429,8 @@ fn testDecl( const func_inst = try decl_block.addFunc(.{ .src_node = node, .param_block = block_inst, - .ret_ty = .void_type, + .ret_ty = &.{}, + .ret_br = 0, .body = fn_block.instructions.items, .cc = .none, .align_inst = .none, @@ -9127,7 +9127,8 @@ const GenZir = struct { src_node: ast.Node.Index, body: []const Zir.Inst.Index, param_block: Zir.Inst.Index, - ret_ty: Zir.Inst.Ref, + ret_ty: []const Zir.Inst.Index, + ret_br: Zir.Inst.Index, cc: Zir.Inst.Ref, align_inst: Zir.Inst.Ref, lib_name: u32, @@ -9137,7 +9138,6 @@ const GenZir = struct { is_extern: bool, }) !Zir.Inst.Ref { assert(args.src_node != 0); - assert(args.ret_ty != .none); const astgen = gz.astgen; const gpa = astgen.gpa; @@ -9179,7 +9179,7 @@ const GenZir = struct { try astgen.extra.ensureUnusedCapacity( gpa, @typeInfo(Zir.Inst.ExtendedFunc).Struct.fields.len + - args.body.len + src_locs.len + + args.ret_ty.len + args.body.len + src_locs.len + @boolToInt(args.lib_name != 0) + @boolToInt(args.align_inst != .none) + @boolToInt(args.cc != .none), @@ -9187,7 +9187,7 @@ const GenZir = struct { const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{ .src_node = gz.nodeIndexToRelative(args.src_node), .param_block = args.param_block, - .return_type = args.ret_ty, + .ret_body_len = @intCast(u32, args.ret_ty.len), .body_len = @intCast(u32, args.body.len), }); if (args.lib_name != 0) { @@ -9199,10 +9199,14 @@ const GenZir = struct { if (args.align_inst != .none) { astgen.extra.appendAssumeCapacity(@enumToInt(args.align_inst)); } + astgen.extra.appendSliceAssumeCapacity(args.ret_ty); astgen.extra.appendSliceAssumeCapacity(args.body); astgen.extra.appendSliceAssumeCapacity(src_locs); const new_index = @intCast(Zir.Inst.Index, astgen.instructions.len); + if (args.ret_br != 0) { + astgen.instructions.items(.data)[args.ret_br].@"break".block_inst = new_index; + } astgen.instructions.appendAssumeCapacity(.{ .tag = .extended, .data = .{ .extended = .{ @@ -9222,23 +9226,27 @@ const GenZir = struct { gz.instructions.appendAssumeCapacity(new_index); return indexToRef(new_index); } else { - try gz.astgen.extra.ensureUnusedCapacity( + try astgen.extra.ensureUnusedCapacity( gpa, @typeInfo(Zir.Inst.Func).Struct.fields.len + - args.body.len + src_locs.len, + args.ret_ty.len + args.body.len + src_locs.len, ); - const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{ + const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.Func{ .param_block = args.param_block, - .return_type = args.ret_ty, + .ret_body_len = @intCast(u32, args.ret_ty.len), .body_len = @intCast(u32, args.body.len), }); - gz.astgen.extra.appendSliceAssumeCapacity(args.body); - gz.astgen.extra.appendSliceAssumeCapacity(src_locs); + astgen.extra.appendSliceAssumeCapacity(args.ret_ty); + astgen.extra.appendSliceAssumeCapacity(args.body); + astgen.extra.appendSliceAssumeCapacity(src_locs); const tag: Zir.Inst.Tag = if (args.is_inferred_error) .func_inferred else .func; - const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); - gz.astgen.instructions.appendAssumeCapacity(.{ + const new_index = @intCast(Zir.Inst.Index, astgen.instructions.len); + if (args.ret_br != 0) { + astgen.instructions.items(.data)[args.ret_br].@"break".block_inst = new_index; + } + astgen.instructions.appendAssumeCapacity(.{ .tag = tag, .data = .{ .pl_node = .{ .src_node = gz.nodeIndexToRelative(args.src_node), diff --git a/src/Module.zig b/src/Module.zig index 2556ad3f0e..da11bc1c3c 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -842,6 +842,9 @@ pub const Fn = struct { pub fn getInferredErrorSet(func: *Fn) ?*std.StringHashMapUnmanaged(void) { const ret_ty = func.owner_decl.ty.fnReturnType(); + if (ret_ty.tag() == .generic_poison) { + return null; + } if (ret_ty.zigTypeTag() == .ErrorUnion) { if (ret_ty.errorUnionSet().castTag(.error_set_inferred)) |payload| { return &payload.data.map; diff --git a/src/Sema.zig b/src/Sema.zig index 0557587fb5..73f22aa845 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2618,128 +2618,130 @@ fn analyzeCall( break :new_func gop.key_ptr.*; }; - try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); - - // Create a Decl for the new function. - const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node); - // TODO better names for generic function instantiations - const name_index = mod.getNextAnonNameIndex(); - new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ - module_fn.owner_decl.name, name_index, - }); - new_decl.src_line = module_fn.owner_decl.src_line; - new_decl.is_pub = module_fn.owner_decl.is_pub; - new_decl.is_exported = module_fn.owner_decl.is_exported; - new_decl.has_align = module_fn.owner_decl.has_align; - new_decl.has_linksection = module_fn.owner_decl.has_linksection; - new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; - new_decl.alive = true; // This Decl is called at runtime. - new_decl.has_tv = true; - new_decl.owns_tv = true; - new_decl.analysis = .in_progress; - new_decl.generation = mod.generation; - - namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); - - var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); - errdefer new_decl_arena.deinit(); - - // Re-run the block that creates the function, with the comptime parameters - // pre-populated inside `inst_map`. This causes `param_comptime` and - // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a - // new, monomorphized function, with the comptime parameters elided. - var child_sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = sema.arena, - .code = fn_zir, - .owner_decl = new_decl, - .namespace = namespace, - .func = null, - .owner_func = null, - .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), - .comptime_args_fn_inst = module_fn.zir_body_inst, - .preallocated_new_func = new_module_func, - }; - defer child_sema.deinit(); - - var child_block: Scope.Block = .{ - .parent = null, - .sema = &child_sema, - .src_decl = new_decl, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - }; - defer { - child_block.instructions.deinit(gpa); - child_block.params.deinit(gpa); - } - - try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); - var arg_i: usize = 0; - for (fn_info.param_body) |inst| { - const is_comptime = switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => false, - else => continue, - } or func_ty_info.paramIsComptime(arg_i); - const arg_src = call_src; // TODO: better source location - const arg = uncasted_args[arg_i]; - if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { - const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else if (is_comptime) { - return sema.failWithNeededComptime(block, arg_src); + { + try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); + + // Create a Decl for the new function. + const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node); + // TODO better names for generic function instantiations + const name_index = mod.getNextAnonNameIndex(); + new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ + module_fn.owner_decl.name, name_index, + }); + new_decl.src_line = module_fn.owner_decl.src_line; + new_decl.is_pub = module_fn.owner_decl.is_pub; + new_decl.is_exported = module_fn.owner_decl.is_exported; + new_decl.has_align = module_fn.owner_decl.has_align; + new_decl.has_linksection = module_fn.owner_decl.has_linksection; + new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; + new_decl.alive = true; // This Decl is called at runtime. + new_decl.has_tv = true; + new_decl.owns_tv = true; + new_decl.analysis = .in_progress; + new_decl.generation = mod.generation; + + namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + + // Re-run the block that creates the function, with the comptime parameters + // pre-populated inside `inst_map`. This causes `param_comptime` and + // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a + // new, monomorphized function, with the comptime parameters elided. + var child_sema: Sema = .{ + .mod = mod, + .gpa = gpa, + .arena = sema.arena, + .code = fn_zir, + .owner_decl = new_decl, + .namespace = namespace, + .func = null, + .owner_func = null, + .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), + .comptime_args_fn_inst = module_fn.zir_body_inst, + .preallocated_new_func = new_module_func, + }; + defer child_sema.deinit(); + + var child_block: Scope.Block = .{ + .parent = null, + .sema = &child_sema, + .src_decl = new_decl, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + }; + defer { + child_block.instructions.deinit(gpa); + child_block.params.deinit(gpa); } - arg_i += 1; - } - const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); - const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); - const new_func = new_func_val.castTag(.function).?.data; - assert(new_func == new_module_func); - arg_i = 0; - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, - else => continue, + try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + const is_comptime = switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime => true, + .param, .param_anytype => false, + else => continue, + } or func_ty_info.paramIsComptime(arg_i); + const arg_src = call_src; // TODO: better source location + const arg = uncasted_args[arg_i]; + if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { + const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else if (is_comptime) { + return sema.failWithNeededComptime(block, arg_src); + } + arg_i += 1; } - const arg = child_sema.inst_map.get(inst).?; - const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; + const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); + const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); + const new_func = new_func_val.castTag(.function).?.data; + assert(new_func == new_module_func); + + arg_i = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, + } + const arg = child_sema.inst_map.get(inst).?; + const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; - if (arg_val.tag() == .generic_poison) { - child_sema.comptime_args[arg_i] = .{ - .ty = Type.initTag(.noreturn), - .val = Value.initTag(.unreachable_value), - }; - } else { - child_sema.comptime_args[arg_i] = .{ - .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), - .val = try arg_val.copy(&new_decl_arena.allocator), - }; + if (arg_val.tag() == .generic_poison) { + child_sema.comptime_args[arg_i] = .{ + .ty = Type.initTag(.noreturn), + .val = Value.initTag(.unreachable_value), + }; + } else { + child_sema.comptime_args[arg_i] = .{ + .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), + .val = try arg_val.copy(&new_decl_arena.allocator), + }; + } + + arg_i += 1; } - arg_i += 1; - } + // Populate the Decl ty/val with the function and its type. + new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); + new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); + new_decl.analysis = .complete; - // Populate the Decl ty/val with the function and its type. - new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); - new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); - new_decl.analysis = .complete; + // The generic function Decl is guaranteed to be the first dependency + // of each of its instantiations. + assert(new_decl.dependencies.keys().len == 0); + try mod.declareDeclDependency(new_decl, module_fn.owner_decl); - // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field - // will be populated, ensuring it will have `analyzeBody` called with the ZIR - // parameters mapped appropriately. - try mod.comp.bin_file.allocateDeclIndexes(new_decl); - try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); + // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field + // will be populated, ensuring it will have `analyzeBody` called with the ZIR + // parameters mapped appropriately. + try mod.comp.bin_file.allocateDeclIndexes(new_decl); + try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); - try new_decl.finalizeNewArena(&new_decl_arena); - - // The generic function Decl is guaranteed to be the first dependency - // of each of its instantiations. - assert(new_decl.dependencies.keys().len == 0); - try mod.declareDeclDependency(new_decl, module_fn.owner_decl); + try new_decl.finalizeNewArena(&new_decl_arena); + } break :res try sema.finishGenericCall( block, @@ -3478,12 +3480,15 @@ fn zirFunc( const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index); + var extra_index = extra.end; + const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; var body_inst: Zir.Inst.Index = 0; var src_locs: Zir.Inst.Func.SrcLocs = undefined; if (extra.data.body_len != 0) { body_inst = inst; - const extra_index = extra.end + extra.data.body_len; + extra_index += extra.data.body_len; src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } @@ -3496,7 +3501,7 @@ fn zirFunc( block, inst_data.src_node, body_inst, - extra.data.return_type, + ret_ty_body, cc, Value.initTag(.null_value), false, @@ -3512,7 +3517,7 @@ fn funcCommon( block: *Scope.Block, src_node_offset: i32, body_inst: Zir.Inst.Index, - zir_return_type: Zir.Inst.Ref, + ret_ty_body: []const Zir.Inst.Index, cc: std.builtin.CallingConvention, align_val: Value, var_args: bool, @@ -3523,7 +3528,37 @@ fn funcCommon( ) CompileError!Air.Inst.Ref { const src: LazySrcLoc = .{ .node_offset = src_node_offset }; const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset }; - const bare_return_type = try sema.resolveType(block, ret_ty_src, zir_return_type); + + // The return type body might be a type expression that depends on generic parameters. + // In such case we need to use a generic_poison value for the return type and mark + // the function as generic. + var is_generic = false; + const bare_return_type: Type = ret_ty: { + if (ret_ty_body.len == 0) break :ret_ty Type.initTag(.void); + + const err = err: { + // Make sure any nested param instructions don't clobber our work. + const prev_params = block.params; + block.params = .{}; + defer { + block.params.deinit(sema.gpa); + block.params = prev_params; + } + if (sema.resolveBody(block, ret_ty_body)) |ret_ty_inst| { + if (sema.analyzeAsType(block, ret_ty_src, ret_ty_inst)) |ret_ty| { + break :ret_ty ret_ty; + } else |err| break :err err; + } else |err| break :err err; + }; + switch (err) { + error.GenericPoison => { + // The type is not available until the generic instantiation. + is_generic = true; + break :ret_ty Type.initTag(.generic_poison); + }, + else => |e| return e, + } + }; const mod = sema.mod; @@ -3540,8 +3575,9 @@ fn funcCommon( const fn_ty: Type = fn_ty: { // Hot path for some common function types. - if (block.params.items.len == 0 and !var_args and align_val.tag() == .null_value and - !inferred_error_set) + // TODO can we eliminate some of these Type tag values? seems unnecessarily complicated. + if (!is_generic and block.params.items.len == 0 and !var_args and + align_val.tag() == .null_value and !inferred_error_set) { if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) { break :fn_ty Type.initTag(.fn_noreturn_no_args); @@ -3560,7 +3596,6 @@ fn funcCommon( } } - var is_generic = false; const param_types = try sema.arena.alloc(Type, block.params.items.len); const comptime_params = try sema.arena.alloc(bool, block.params.items.len); for (block.params.items) |param, i| { @@ -3574,7 +3609,9 @@ fn funcCommon( return mod.fail(&block.base, src, "TODO implement support for function prototypes to have alignment specified", .{}); } - const return_type = if (!inferred_error_set) bare_return_type else blk: { + const return_type = if (!inferred_error_set or bare_return_type.tag() == .generic_poison) + bare_return_type + else blk: { const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, .{ .func = new_func, .map = .{}, @@ -6944,6 +6981,9 @@ fn zirFuncExtended( break :blk align_tv.val; } else Value.initTag(.null_value); + const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + var body_inst: Zir.Inst.Index = 0; var src_locs: Zir.Inst.Func.SrcLocs = undefined; if (extra.data.body_len != 0) { @@ -6960,7 +7000,7 @@ fn zirFuncExtended( block, extra.data.src_node, body_inst, - extra.data.return_type, + ret_ty_body, cc, align_val, is_var_args, diff --git a/src/Zir.zig b/src/Zir.zig index 862af7b033..094cef3393 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2272,11 +2272,13 @@ pub const Inst = struct { /// 0. lib_name: u32, // null terminated string index, if has_lib_name is set /// 1. cc: Ref, // if has_cc is set /// 2. align: Ref, // if has_align is set - /// 3. body: Index // for each body_len - /// 4. src_locs: Func.SrcLocs // if body_len != 0 + /// 3. return_type: Index // for each ret_body_len + /// 4. body: Index // for each body_len + /// 5. src_locs: Func.SrcLocs // if body_len != 0 pub const ExtendedFunc = struct { src_node: i32, - return_type: Ref, + /// If this is 0 it means a void return type. + ret_body_len: u32, /// Points to the block that contains the param instructions for this function. param_block: Index, body_len: u32, @@ -2312,10 +2314,12 @@ pub const Inst = struct { }; /// Trailing: - /// 0. body: Index // for each body_len - /// 1. src_locs: SrcLocs // if body_len != 0 + /// 0. return_type: Index // for each ret_body_len + /// 1. body: Index // for each body_len + /// 2. src_locs: SrcLocs // if body_len != 0 pub const Func = struct { - return_type: Ref, + /// If this is 0 it means a void return type. + ret_body_len: u32, /// Points to the block that contains the param instructions for this function. param_block: Index, body_len: u32, @@ -4344,15 +4348,21 @@ const Writer = struct { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const extra = self.code.extraData(Inst.Func, inst_data.payload_index); - const body = self.code.extra[extra.end..][0..extra.data.body_len]; + var extra_index = extra.end; + + const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + + const body = self.code.extra[extra_index..][0..extra.data.body_len]; + extra_index += body.len; + var src_locs: Zir.Inst.Func.SrcLocs = undefined; if (body.len != 0) { - const extra_index = extra.end + body.len; src_locs = self.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } return self.writeFuncCommon( stream, - extra.data.return_type, + ret_ty_body, inferred_error_set, false, false, @@ -4387,6 +4397,9 @@ const Writer = struct { break :blk align_inst; }; + const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + const body = self.code.extra[extra_index..][0..extra.data.body_len]; extra_index += body.len; @@ -4396,7 +4409,7 @@ const Writer = struct { } return self.writeFuncCommon( stream, - extra.data.return_type, + ret_ty_body, small.is_inferred_error, small.is_var_args, small.is_extern, @@ -4478,7 +4491,7 @@ const Writer = struct { fn writeFuncCommon( self: *Writer, stream: anytype, - ret_ty: Inst.Ref, + ret_ty_body: []const Inst.Index, inferred_error_set: bool, var_args: bool, is_extern: bool, @@ -4488,7 +4501,13 @@ const Writer = struct { src: LazySrcLoc, src_locs: Zir.Inst.Func.SrcLocs, ) !void { - try self.writeInstRef(stream, ret_ty); + try stream.writeAll("ret_ty={\n"); + self.indent += 2; + try self.writeBody(stream, ret_ty_body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + try self.writeOptionalInstRef(stream, ", cc=", cc); try self.writeOptionalInstRef(stream, ", align=", align_inst); try self.writeFlag(stream, ", vargs", var_args); @@ -4496,9 +4515,9 @@ const Writer = struct { try self.writeFlag(stream, ", inferror", inferred_error_set); if (body.len == 0) { - try stream.writeAll(", {}) "); + try stream.writeAll(", body={}) "); } else { - try stream.writeAll(", {\n"); + try stream.writeAll(", body={\n"); self.indent += 2; try self.writeBody(stream, body); self.indent -= 2; @@ -4932,6 +4951,7 @@ fn findDeclsBody( pub const FnInfo = struct { param_body: []const Inst.Index, + ret_ty_body: []const Inst.Index, body: []const Inst.Index, total_params_len: u32, }; @@ -4942,13 +4962,22 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { const info: struct { param_block: Inst.Index, body: []const Inst.Index, + ret_ty_body: []const Inst.Index, } = switch (tags[fn_inst]) { .func, .func_inferred => blk: { const inst_data = datas[fn_inst].pl_node; const extra = zir.extraData(Inst.Func, inst_data.payload_index); - const body = zir.extra[extra.end..][0..extra.data.body_len]; + var extra_index: usize = extra.end; + + const ret_ty_body = zir.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + + const body = zir.extra[extra_index..][0..extra.data.body_len]; + extra_index += body.len; + break :blk .{ .param_block = extra.data.param_block, + .ret_ty_body = ret_ty_body, .body = body, }; }, @@ -4961,9 +4990,13 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { extra_index += @boolToInt(small.has_lib_name); extra_index += @boolToInt(small.has_cc); extra_index += @boolToInt(small.has_align); + const ret_ty_body = zir.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; const body = zir.extra[extra_index..][0..extra.data.body_len]; + extra_index += body.len; break :blk .{ .param_block = extra.data.param_block, + .ret_ty_body = ret_ty_body, .body = body, }; }, @@ -4983,6 +5016,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { } return .{ .param_body = param_body, + .ret_ty_body = info.ret_ty_body, .body = info.body, .total_params_len = total_params_len, }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0e73469687..91b48ffcfb 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -575,6 +575,14 @@ pub const DeclGen = struct { const info = t.intInfo(self.module.getTarget()); return self.context.intType(info.bits); }, + .Float => switch (t.floatBits(self.module.getTarget())) { + 16 => return self.context.halfType(), + 32 => return self.context.floatType(), + 64 => return self.context.doubleType(), + 80 => return self.context.x86FP80Type(), + 128 => return self.context.fp128Type(), + else => unreachable, + }, .Bool => return self.context.intType(1), .Pointer => { if (t.isSlice()) { @@ -661,7 +669,6 @@ pub const DeclGen = struct { .BoundFn => @panic("TODO remove BoundFn from the language"), - .Float, .Enum, .Union, .Opaque, @@ -699,6 +706,13 @@ pub const DeclGen = struct { } return llvm_int; }, + .Float => { + if (tv.ty.floatBits(self.module.getTarget()) <= 64) { + const llvm_ty = try self.llvmType(tv.ty); + return llvm_ty.constReal(tv.val.toFloat(f64)); + } + return self.todo("bitcast to f128 from an integer", .{}); + }, .Pointer => switch (tv.val.tag()) { .decl_ref => { if (tv.ty.isSlice()) { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 4af3cadd84..675c5539fb 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -31,6 +31,21 @@ pub const Context = opaque { pub const intType = LLVMIntTypeInContext; extern fn LLVMIntTypeInContext(C: *const Context, NumBits: c_uint) *const Type; + pub const halfType = LLVMHalfTypeInContext; + extern fn LLVMHalfTypeInContext(C: *const Context) *const Type; + + pub const floatType = LLVMFloatTypeInContext; + extern fn LLVMFloatTypeInContext(C: *const Context) *const Type; + + pub const doubleType = LLVMDoubleTypeInContext; + extern fn LLVMDoubleTypeInContext(C: *const Context) *const Type; + + pub const x86FP80Type = LLVMX86FP80TypeInContext; + extern fn LLVMX86FP80TypeInContext(C: *const Context) *const Type; + + pub const fp128Type = LLVMFP128TypeInContext; + extern fn LLVMFP128TypeInContext(C: *const Context) *const Type; + pub const voidType = LLVMVoidTypeInContext; extern fn LLVMVoidTypeInContext(C: *const Context) *const Type; @@ -127,6 +142,9 @@ pub const Type = opaque { pub const constInt = LLVMConstInt; extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value; + pub const constReal = LLVMConstReal; + extern fn LLVMConstReal(RealTy: *const Type, N: f64) *const Value; + pub const constArray = LLVMConstArray; extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: [*]*const Value, Length: c_uint) *const Value; diff --git a/src/print_air.zig b/src/print_air.zig index 00317b26e8..11f2982fc3 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -222,7 +222,7 @@ const Writer = struct { const extra = w.air.extraData(Air.Block, ty_pl.payload); const body = w.air.extra[extra.end..][0..extra.data.body_len]; - try s.writeAll("{\n"); + try s.print("{}, {{\n", .{w.air.getRefType(ty_pl.ty)}); const old_indent = w.indent; w.indent += 2; try w.writeBody(s, body); diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index e3fed907df..a3c5668b39 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; @@ -14,3 +15,58 @@ test "one param, explicit comptime" { fn checkSize(comptime T: type) usize { return @sizeOf(T); } + +test "simple generic fn" { + try expect(max(i32, 3, -1) == 3); + try expect(max(u8, 1, 100) == 100); + if (!builtin.zig_is_stage2) { + // TODO: stage2 is incorrectly emitting the following: + // error: cast of value 1.23e-01 to type 'f32' loses information + try expect(max(f32, 0.123, 0.456) == 0.456); + } + try expect(add(2, 3) == 5); +} + +fn max(comptime T: type, a: T, b: T) T { + if (!builtin.zig_is_stage2) { + // TODO: stage2 is incorrectly emitting AIR that allocates a result + // value, stores to it, but then returns void instead of the result. + return if (a > b) a else b; + } + if (a > b) { + return a; + } else { + return b; + } +} + +fn add(comptime a: i32, b: i32) i32 { + return (comptime a) + b; +} + +const the_max = max(u32, 1234, 5678); +test "compile time generic eval" { + try expect(the_max == 5678); +} + +fn gimmeTheBigOne(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn shouldCallSameInstance(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn sameButWithFloats(a: f64, b: f64) f64 { + return max(f64, a, b); +} + +test "fn with comptime args" { + try expect(gimmeTheBigOne(1234, 5678) == 5678); + try expect(shouldCallSameInstance(34, 12) == 34); + if (!builtin.zig_is_stage2) { + // TODO: stage2 llvm backend needs to use fcmp instead of icmp + // probably AIR should just have different instructions for floats. + try expect(sameButWithFloats(0.43, 0.49) == 0.49); + } +} diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig index 104752607a..c9f274c7c1 100644 --- a/test/behavior/generics_stage1.zig +++ b/test/behavior/generics_stage1.zig @@ -3,44 +3,7 @@ const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; -test "simple generic fn" { - try expect(max(i32, 3, -1) == 3); - try expect(max(f32, 0.123, 0.456) == 0.456); - try expect(add(2, 3) == 5); -} - -fn max(comptime T: type, a: T, b: T) T { - return if (a > b) a else b; -} - -fn add(comptime a: i32, b: i32) i32 { - return (comptime a) + b; -} - -const the_max = max(u32, 1234, 5678); -test "compile time generic eval" { - try expect(the_max == 5678); -} - -fn gimmeTheBigOne(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn shouldCallSameInstance(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn sameButWithFloats(a: f64, b: f64) f64 { - return max(f64, a, b); -} - -test "fn with comptime args" { - try expect(gimmeTheBigOne(1234, 5678) == 5678); - try expect(shouldCallSameInstance(34, 12) == 34); - try expect(sameButWithFloats(0.43, 0.49) == 0.49); -} - -test "var params" { +test "anytype params" { try expect(max_i32(12, 34) == 34); try expect(max_f64(1.2, 3.4) == 3.4); } -- cgit v1.2.3 From ede76f4fe31e6fc935ae10f030eb2c97ed36aaaa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Aug 2021 16:24:39 -0700 Subject: stage2: fix generics with non-comptime anytype parameters The `comptime_args` field of Fn has a clarified purpose: For generic function instantiations, there is a `TypedValue` here for each parameter of the function: * Non-comptime parameters are marked with a `generic_poison` for the value. * Non-anytype parameters are marked with a `generic_poison` for the type. Sema now has a `fn_ret_ty` field. Doc comments reproduced here: > When semantic analysis needs to know the return type of the function whose body > is being analyzed, this `Type` should be used instead of going through `func`. > This will correctly handle the case of a comptime/inline function call of a > generic function which uses a type expression for the return type. > The type will be `void` in the case that `func` is `null`. Various places in Sema are modified in accordance with this guidance. Fixed `resolveMaybeUndefVal` not returning `error.GenericPoison` when Value Tag of `generic_poison` is encountered. Fixed generic function memoization incorrect equality checking. The logic now clearly deals properly with any combination of anytype and comptime parameters. Fixed not removing generic function instantiation from the table in case a compile errors in the rest of `call` semantic analysis. This required introduction of yet another adapter which I have called `GenericRemoveAdapter`. This one is nice and simple - it's the same hash function (the same precomputed hash is passed in) but the equality function checks pointers rather than doing any logic. Inline/comptime function calls coerce each argument in accordance with the function parameter type expressions. Likewise the return type expression is evaluated and provided (see `fn_ret_ty` above). There's a new compile error "unable to monomorphize function". It's pretty unhelpful and will need to get improved in the future. It happens when a type expression in a generic function did not end up getting resolved at a callsite. This can happen, for example, if a runtime parameter is attempted to be used where it needed to be comptime known: ```zig fn foo(x: anytype) [x]u8 { _ = x; } ``` In this example, even if we pass a number such as `10` for `x`, it is not marked `comptime`, so `x` will have a runtime known value, making the return type unable to resolve. In the LLVM backend I implement cmp instructions for float types to pass some behavior tests that used floats. --- src/Module.zig | 12 +- src/Sema.zig | 319 +++++++++++++++++++++++++------------- src/codegen/llvm.zig | 41 +++-- src/codegen/llvm/bindings.zig | 22 ++- test/behavior/generics.zig | 38 ++++- test/behavior/generics_stage1.zig | 22 --- 6 files changed, 303 insertions(+), 151 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Module.zig b/src/Module.zig index a694f775da..3652a27927 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -801,8 +801,9 @@ pub const Fn = struct { /// The Decl that corresponds to the function itself. owner_decl: *Decl, /// If this is not null, this function is a generic function instantiation, and - /// there is a `Value` here for each parameter of the function. Non-comptime - /// parameters are marked with an `unreachable_value`. + /// there is a `TypedValue` here for each parameter of the function. + /// Non-comptime parameters are marked with a `generic_poison` for the value. + /// Non-anytype parameters are marked with a `generic_poison` for the type. comptime_args: ?[*]TypedValue = null, /// The ZIR instruction that is a function instruction. Use this to find /// the body. We store this rather than the body directly so that when ZIR @@ -2975,6 +2976,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { .owner_decl = new_decl, .namespace = &struct_obj.namespace, .func = null, + .fn_ret_ty = Type.initTag(.void), .owner_func = null, }; defer sema.deinit(); @@ -3029,6 +3031,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { .owner_decl = decl, .namespace = decl.namespace, .func = null, + .fn_ret_ty = Type.initTag(.void), .owner_func = null, }; defer sema.deinit(); @@ -3712,6 +3715,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { .owner_decl = decl, .namespace = decl.namespace, .func = func, + .fn_ret_ty = func.owner_decl.ty.fnReturnType(), .owner_func = func, }; defer sema.deinit(); @@ -3764,7 +3768,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { }; if (func.comptime_args) |comptime_args| { const arg_tv = comptime_args[total_param_index]; - if (arg_tv.val.tag() != .unreachable_value) { + if (arg_tv.val.tag() != .generic_poison) { // We have a comptime value for this parameter. const arg = try sema.addConstant(arg_tv.ty, arg_tv.val); sema.inst_map.putAssumeCapacityNoClobber(inst, arg); @@ -4447,6 +4451,7 @@ pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void .namespace = &struct_obj.namespace, .owner_func = null, .func = null, + .fn_ret_ty = Type.initTag(.void), }; defer sema.deinit(); @@ -4600,6 +4605,7 @@ pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void { .namespace = &union_obj.namespace, .owner_func = null, .func = null, + .fn_ret_ty = Type.initTag(.void), }; defer sema.deinit(); diff --git a/src/Sema.zig b/src/Sema.zig index 825737e4c5..8bf4111566 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -29,6 +29,12 @@ owner_func: ?*Module.Fn, /// This starts out the same as `owner_func` and then diverges in the case of /// an inline or comptime function call. func: ?*Module.Fn, +/// When semantic analysis needs to know the return type of the function whose body +/// is being analyzed, this `Type` should be used instead of going through `func`. +/// This will correctly handle the case of a comptime/inline function call of a +/// generic function which uses a type expression for the return type. +/// The type will be `void` in the case that `func` is `null`. +fn_ret_ty: Type, branch_quota: u32 = 1000, branch_count: u32 = 0, /// This field is updated when a new source location becomes active, so that @@ -628,6 +634,7 @@ fn analyzeAsType( /// May return Value Tags: `variable`, `undef`. /// See `resolveConstValue` for an alternative. +/// Value Tag `generic_poison` causes `error.GenericPoison` to be returned. fn resolveValue( sema: *Sema, block: *Scope.Block, @@ -679,6 +686,7 @@ fn resolveDefinedValue( /// Value Tag `variable` causes this function to return `null`. /// Value Tag `undef` causes this function to return the Value. +/// Value Tag `generic_poison` causes `error.GenericPoison` to be returned. fn resolveMaybeUndefVal( sema: *Sema, block: *Scope.Block, @@ -686,10 +694,11 @@ fn resolveMaybeUndefVal( inst: Air.Inst.Ref, ) CompileError!?Value { const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; - if (val.tag() == .variable) { - return null; + switch (val.tag()) { + .variable => return null, + .generic_poison => return error.GenericPoison, + else => return val, } - return val; } /// Returns all Value tags including `variable` and `undef`. @@ -1033,6 +1042,7 @@ fn zirEnumDecl( .namespace = &enum_obj.namespace, .owner_func = null, .func = null, + .fn_ret_ty = Type.initTag(.void), .branch_quota = sema.branch_quota, .branch_count = sema.branch_count, }; @@ -1238,9 +1248,7 @@ fn zirRetPtr( const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; try sema.requireFunctionBlock(block, src); - const fn_ty = sema.func.?.owner_decl.ty; - const ret_type = fn_ty.fnReturnType(); - const ptr_type = try Module.simplePtrType(sema.arena, ret_type, true, .One); + const ptr_type = try Module.simplePtrType(sema.arena, sema.fn_ret_ty, true, .One); return block.addTy(.alloc, ptr_type); } @@ -1263,9 +1271,7 @@ fn zirRetType( const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; try sema.requireFunctionBlock(block, src); - const fn_ty = sema.func.?.owner_decl.ty; - const ret_type = fn_ty.fnReturnType(); - return sema.addType(ret_type); + return sema.addType(sema.fn_ret_ty); } fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { @@ -2364,7 +2370,7 @@ const GenericCallAdapter = struct { generic_fn: *Module.Fn, precomputed_hash: u64, func_ty_info: Type.Payload.Function.Data, - comptime_vals: []const Value, + comptime_tvs: []const TypedValue, pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool { _ = adapted_key; @@ -2373,12 +2379,22 @@ const GenericCallAdapter = struct { const generic_owner_decl = other_key.owner_decl.dependencies.keys()[0]; if (ctx.generic_fn.owner_decl != generic_owner_decl) return false; - // This logic must be kept in sync with the logic in `analyzeCall` that - // computes the hash. const other_comptime_args = other_key.comptime_args.?; - for (ctx.func_ty_info.param_types) |param_ty, i| { - if (ctx.func_ty_info.paramIsComptime(i) and param_ty.tag() != .generic_poison) { - if (!ctx.comptime_vals[i].eql(other_comptime_args[i].val, param_ty)) { + for (other_comptime_args[0..ctx.func_ty_info.param_types.len]) |other_arg, i| { + if (other_arg.ty.tag() != .generic_poison) { + // anytype parameter + if (!other_arg.ty.eql(ctx.comptime_tvs[i].ty)) { + return false; + } + } + if (other_arg.val.tag() != .generic_poison) { + // comptime parameter + if (ctx.comptime_tvs[i].val.tag() == .generic_poison) { + // No match because the instantiation has a comptime parameter + // but the callsite does not. + return false; + } + if (!other_arg.val.eql(ctx.comptime_tvs[i].val, other_arg.ty)) { return false; } } @@ -2394,6 +2410,22 @@ const GenericCallAdapter = struct { } }; +const GenericRemoveAdapter = struct { + precomputed_hash: u64, + + pub fn eql(ctx: @This(), adapted_key: *Module.Fn, other_key: *Module.Fn) bool { + _ = ctx; + return adapted_key == other_key; + } + + /// The implementation of the hash is in semantic analysis of function calls, so + /// that any errors when computing the hash can be properly reported. + pub fn hash(ctx: @This(), adapted_key: *Module.Fn) u64 { + _ = adapted_key; + return ctx.precomputed_hash; + } +}; + fn analyzeCall( sema: *Sema, block: *Scope.Block, @@ -2466,14 +2498,6 @@ fn analyzeCall( const is_inline_call = is_comptime_call or modifier == .always_inline or func_ty_info.cc == .Inline; const result: Air.Inst.Ref = if (is_inline_call) res: { - // TODO look into not allocating this args array - const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); - for (uncasted_args) |uncasted_arg, i| { - const param_ty = func_ty.fnParamType(i); - const arg_src = call_src; // TODO: better source location - args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src); - } - const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = switch (func_val.tag()) { .function => func_val.castTag(.function).?.data, @@ -2544,19 +2568,62 @@ fn analyzeCall( // This will have return instructions analyzed as break instructions to // the block_inst above. Here we are performing "comptime/inline semantic analysis" // for a function body, which means we must map the parameter ZIR instructions to - // the AIR instructions of the callsite. + // the AIR instructions of the callsite. The callee could be a generic function + // which means its parameter type expressions must be resolved in order and used + // to successively coerce the arguments. const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); const zir_tags = sema.code.instructions.items(.tag); var arg_i: usize = 0; - try sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, args.len)); - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param, .param_comptime, .param_anytype, .param_anytype_comptime => {}, - else => continue, + for (fn_info.param_body) |inst| switch (zir_tags[inst]) { + .param, .param_comptime => { + // Evaluate the parameter type expression now that previous ones have + // been mapped, and coerce the corresponding argument to it. + const pl_tok = sema.code.instructions.items(.data)[inst].pl_tok; + const param_src = pl_tok.src(); + const extra = sema.code.extraData(Zir.Inst.Param, pl_tok.payload_index); + const param_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + const param_ty_inst = try sema.resolveBody(&child_block, param_body); + const param_ty = try sema.analyzeAsType(&child_block, param_src, param_ty_inst); + const arg_src = call_src; // TODO: better source location + const casted_arg = try sema.coerce(&child_block, param_ty, uncasted_args[arg_i], arg_src); + try sema.inst_map.putNoClobber(gpa, inst, casted_arg); + arg_i += 1; + continue; + }, + .param_anytype, .param_anytype_comptime => { + // No coercion needed. + try sema.inst_map.putNoClobber(gpa, inst, uncasted_args[arg_i]); + arg_i += 1; + continue; + }, + else => continue, + }; + + // In case it is a generic function with an expression for the return type that depends + // on parameters, we must now do the same for the return type as we just did with + // each of the parameters, resolving the return type and providing it to the child + // `Sema` so that it can be used for the `ret_ptr` instruction. + const ret_ty_inst = try sema.resolveBody(&child_block, fn_info.ret_ty_body); + const ret_ty_src = func_src; // TODO better source location + const bare_return_type = try sema.analyzeAsType(&child_block, ret_ty_src, ret_ty_inst); + // If the function has an inferred error set, `bare_return_type` is the payload type only. + const fn_ret_ty = blk: { + // TODO instead of reusing the function's inferred error set, this code should + // create a temporary error set which is used for the comptime/inline function + // call alone, independent from the runtime instantiation. + if (func_ty_info.return_type.castTag(.error_union)) |payload| { + const error_set_ty = payload.data.error_set; + break :blk try Type.Tag.error_union.create(sema.arena, .{ + .error_set = error_set_ty, + .payload = bare_return_type, + }); } - sema.inst_map.putAssumeCapacityNoClobber(inst, args[arg_i]); - arg_i += 1; - } + break :blk bare_return_type; + }; + const parent_fn_ret_ty = sema.fn_ret_ty; + sema.fn_ret_ty = fn_ret_ty; + defer sema.fn_ret_ty = parent_fn_ret_ty; + _ = try sema.analyzeBody(&child_block, fn_info.body); break :res try sema.analyzeBlockBody(block, call_src, &child_block, merges); } else if (func_ty_info.is_generic) res: { @@ -2569,57 +2636,74 @@ fn analyzeCall( const fn_zir = namespace.file_scope.zir; const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst); const zir_tags = fn_zir.instructions.items(.tag); - const new_module_func = new_func: { - // This hash must match `Module.MonomorphedFuncsContext.hash`. - // For parameters explicitly marked comptime and simple parameter type expressions, - // we know whether a parameter is elided from a monomorphed function, and can - // use it in the hash here. However, for parameter type expressions that are not - // explicitly marked comptime and rely on previous parameter comptime values, we - // don't find out until after generating a monomorphed function whether the parameter - // type ended up being a "must-be-comptime-known" type. - var hasher = std.hash.Wyhash.init(0); - std.hash.autoHash(&hasher, @ptrToInt(module_fn)); - - const comptime_vals = try sema.arena.alloc(Value, func_ty_info.param_types.len); - - for (func_ty_info.param_types) |param_ty, i| { - const is_comptime = func_ty_info.paramIsComptime(i); - if (is_comptime and param_ty.tag() != .generic_poison) { - const arg_src = call_src; // TODO better source location - const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src); - if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| { + + // This hash must match `Module.MonomorphedFuncsContext.hash`. + // For parameters explicitly marked comptime and simple parameter type expressions, + // we know whether a parameter is elided from a monomorphed function, and can + // use it in the hash here. However, for parameter type expressions that are not + // explicitly marked comptime and rely on previous parameter comptime values, we + // don't find out until after generating a monomorphed function whether the parameter + // type ended up being a "must-be-comptime-known" type. + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, @ptrToInt(module_fn)); + + const comptime_tvs = try sema.arena.alloc(TypedValue, func_ty_info.param_types.len); + + for (func_ty_info.param_types) |param_ty, i| { + const is_comptime = func_ty_info.paramIsComptime(i); + if (is_comptime) { + const arg_src = call_src; // TODO better source location + const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src); + if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| { + if (param_ty.tag() != .generic_poison) { arg_val.hash(param_ty, &hasher); - comptime_vals[i] = arg_val; - } else { - return sema.failWithNeededComptime(block, arg_src); } + comptime_tvs[i] = .{ + // This will be different than `param_ty` in the case of `generic_poison`. + .ty = sema.typeOf(casted_arg), + .val = arg_val, + }; + } else { + return sema.failWithNeededComptime(block, arg_src); } + } else { + comptime_tvs[i] = .{ + .ty = sema.typeOf(uncasted_args[i]), + .val = Value.initTag(.generic_poison), + }; } + } - const adapter: GenericCallAdapter = .{ - .generic_fn = module_fn, - .precomputed_hash = hasher.final(), - .func_ty_info = func_ty_info, - .comptime_vals = comptime_vals, - }; - const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); - if (gop.found_existing) { - const callee_func = gop.key_ptr.*; - break :res try sema.finishGenericCall( - block, - call_src, - callee_func, - func_src, - uncasted_args, - fn_info, - zir_tags, - ); - } - gop.key_ptr.* = try gpa.create(Module.Fn); - break :new_func gop.key_ptr.*; - }; + const precomputed_hash = hasher.final(); + const adapter: GenericCallAdapter = .{ + .generic_fn = module_fn, + .precomputed_hash = precomputed_hash, + .func_ty_info = func_ty_info, + .comptime_tvs = comptime_tvs, + }; + const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); + if (gop.found_existing) { + const callee_func = gop.key_ptr.*; + break :res try sema.finishGenericCall( + block, + call_src, + callee_func, + func_src, + uncasted_args, + fn_info, + zir_tags, + ); + } + const new_module_func = try gpa.create(Module.Fn); + gop.key_ptr.* = new_module_func; { + errdefer gpa.destroy(new_module_func); + const remove_adapter: GenericRemoveAdapter = .{ + .precomputed_hash = precomputed_hash, + }; + errdefer assert(mod.monomorphed_funcs.removeAdapted(new_module_func, remove_adapter)); + try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); // Create a Decl for the new function. @@ -2658,6 +2742,7 @@ fn analyzeCall( .owner_decl = new_decl, .namespace = namespace, .func = null, + .fn_ret_ty = Type.initTag(.void), .owner_func = null, .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), .comptime_args_fn_inst = module_fn.zir_body_inst, @@ -2681,11 +2766,25 @@ fn analyzeCall( try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); var arg_i: usize = 0; for (fn_info.param_body) |inst| { - const is_comptime = switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => false, + var is_comptime = false; + var is_anytype = false; + switch (zir_tags[inst]) { + .param => { + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_comptime => { + is_comptime = true; + }, + .param_anytype => { + is_anytype = true; + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_anytype_comptime => { + is_anytype = true; + is_comptime = true; + }, else => continue, - } or func_ty_info.paramIsComptime(arg_i); + } const arg_src = call_src; // TODO: better source location const arg = uncasted_args[arg_i]; if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { @@ -2693,6 +2792,12 @@ fn analyzeCall( child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); } else if (is_comptime) { return sema.failWithNeededComptime(block, arg_src); + } else if (is_anytype) { + const child_arg = try child_sema.addConstant( + sema.typeOf(arg), + Value.initTag(.generic_poison), + ); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); } arg_i += 1; } @@ -2710,17 +2815,10 @@ fn analyzeCall( const arg = child_sema.inst_map.get(inst).?; const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; - if (arg_val.tag() == .generic_poison) { - child_sema.comptime_args[arg_i] = .{ - .ty = Type.initTag(.noreturn), - .val = Value.initTag(.unreachable_value), - }; - } else { - child_sema.comptime_args[arg_i] = .{ - .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), - .val = try arg_val.copy(&new_decl_arena.allocator), - }; - } + child_sema.comptime_args[arg_i] = .{ + .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), + .val = try arg_val.copy(&new_decl_arena.allocator), + }; arg_i += 1; } @@ -2730,6 +2828,18 @@ fn analyzeCall( new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); new_decl.analysis = .complete; + if (new_decl.ty.fnInfo().is_generic) { + // TODO improve this error message. This can happen because of the parameter + // type expression or return type expression depending on runtime-provided values. + // The error message should be emitted in zirParam or funcCommon when it + // is determined that we are trying to instantiate a generic function. + return mod.fail(&block.base, call_src, "unable to monomorphize function", .{}); + } + + log.debug("generic function '{s}' instantiated with type {}", .{ + new_decl.name, new_decl.ty, + }); + // The generic function Decl is guaranteed to be the first dependency // of each of its instantiations. assert(new_decl.dependencies.keys().len == 0); @@ -2809,7 +2919,7 @@ fn finishGenericCall( for (fn_info.param_body) |inst| { switch (zir_tags[inst]) { .param_comptime, .param_anytype_comptime, .param, .param_anytype => { - if (comptime_args[arg_i].val.tag() == .unreachable_value) { + if (comptime_args[arg_i].val.tag() == .generic_poison) { count += 1; } arg_i += 1; @@ -2829,7 +2939,7 @@ fn finishGenericCall( .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, else => continue, } - const is_runtime = comptime_args[total_i].val.tag() == .unreachable_value; + const is_runtime = comptime_args[total_i].val.tag() == .generic_poison; if (is_runtime) { const param_ty = new_fn_ty.fnParamType(runtime_i); const arg_src = call_src; // TODO: better source location @@ -6162,28 +6272,23 @@ fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr fn analyzeRet( sema: *Sema, block: *Scope.Block, - operand: Air.Inst.Ref, + uncasted_operand: Air.Inst.Ref, src: LazySrcLoc, need_coercion: bool, ) CompileError!Zir.Inst.Index { - const casted_operand = if (!need_coercion) operand else op: { - const func = sema.func.?; - const fn_ty = func.owner_decl.ty; - // TODO: In the case of a comptime/inline function call of a generic function, - // this needs to be the resolved return type based on the function parameter type - // expressions being evaluated with comptime arguments passed in. Otherwise, this - // ends up being .generic_poison and failing the comptime/inline function call analysis. - const fn_ret_ty = fn_ty.fnReturnType(); - break :op try sema.coerce(block, fn_ret_ty, operand, src); - }; + const operand = if (!need_coercion) + uncasted_operand + else + try sema.coerce(block, sema.fn_ret_ty, uncasted_operand, src); + if (block.inlining) |inlining| { // We are inlining a function call; rewrite the `ret` as a `break`. - try inlining.merges.results.append(sema.gpa, casted_operand); - _ = try block.addBr(inlining.merges.block_inst, casted_operand); + try inlining.merges.results.append(sema.gpa, operand); + _ = try block.addBr(inlining.merges.block_inst, operand); return always_noreturn; } - _ = try block.addUnOp(.ret, casted_operand); + _ = try block.addUnOp(.ret, operand); return always_noreturn; } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 91b48ffcfb..4b7454d878 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1093,21 +1093,32 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (!inst_ty.isInt()) - if (inst_ty.tag() != .bool) - return self.todo("implement 'airCmp' for type {}", .{inst_ty}); - - const is_signed = inst_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, ""); + switch (self.air.typeOf(bin_op.lhs).zigTypeTag()) { + .Int, .Bool, .Pointer => { + const is_signed = inst_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, ""); + }, + .Float => { + const operation: llvm.RealPredicate = switch (op) { + .eq => .OEQ, + .neq => .UNE, + .lt => .OLT, + .lte => .OLE, + .gt => .OGT, + .gte => .OGE, + }; + return self.builder.buildFCmp(operation, lhs, rhs, ""); + }, + else => unreachable, + } } fn airBlock(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 675c5539fb..096cd4a5b4 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -409,6 +409,9 @@ pub const Builder = opaque { 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 buildFCmp = LLVMBuildFCmp; + extern fn LLVMBuildFCmp(*const Builder, Op: RealPredicate, 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; @@ -451,7 +454,7 @@ pub const Builder = opaque { ) *const Value; }; -pub const IntPredicate = enum(c_int) { +pub const IntPredicate = enum(c_uint) { EQ = 32, NE = 33, UGT = 34, @@ -464,6 +467,23 @@ pub const IntPredicate = enum(c_int) { SLE = 41, }; +pub const RealPredicate = enum(c_uint) { + OEQ = 1, + OGT = 2, + OGE = 3, + OLT = 4, + OLE = 5, + ONE = 6, + ORD = 7, + UNO = 8, + UEQ = 9, + UGT = 10, + UGE = 11, + ULT = 12, + ULE = 13, + UNE = 14, +}; + pub const BasicBlock = opaque { pub const deleteBasicBlock = LLVMDeleteBasicBlock; extern fn LLVMDeleteBasicBlock(BB: *const BasicBlock) void; diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index a3c5668b39..bb5504b479 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -64,9 +64,41 @@ fn sameButWithFloats(a: f64, b: f64) f64 { test "fn with comptime args" { try expect(gimmeTheBigOne(1234, 5678) == 5678); try expect(shouldCallSameInstance(34, 12) == 34); + try expect(sameButWithFloats(0.43, 0.49) == 0.49); +} + +test "anytype params" { + try expect(max_i32(12, 34) == 34); + try expect(max_f64(1.2, 3.4) == 3.4); if (!builtin.zig_is_stage2) { - // TODO: stage2 llvm backend needs to use fcmp instead of icmp - // probably AIR should just have different instructions for floats. - try expect(sameButWithFloats(0.43, 0.49) == 0.49); + // TODO: stage2 is incorrectly hitting the following problem: + // error: unable to resolve comptime value + // return max_anytype(a, b); + // ^ + comptime { + try expect(max_i32(12, 34) == 34); + try expect(max_f64(1.2, 3.4) == 3.4); + } + } +} + +fn max_anytype(a: anytype, b: anytype) @TypeOf(a, b) { + if (!builtin.zig_is_stage2) { + // TODO: stage2 is incorrectly emitting AIR that allocates a result + // value, stores to it, but then returns void instead of the result. + return if (a > b) a else b; } + if (a > b) { + return a; + } else { + return b; + } +} + +fn max_i32(a: i32, b: i32) i32 { + return max_anytype(a, b); +} + +fn max_f64(a: f64, b: f64) f64 { + return max_anytype(a, b); } diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig index 7ee263b1b9..4b768a5b4f 100644 --- a/test/behavior/generics_stage1.zig +++ b/test/behavior/generics_stage1.zig @@ -3,28 +3,6 @@ const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; -test "anytype params" { - try expect(max_i32(12, 34) == 34); - try expect(max_f64(1.2, 3.4) == 3.4); -} - -test { - comptime try expect(max_i32(12, 34) == 34); - comptime try expect(max_f64(1.2, 3.4) == 3.4); -} - -fn max_anytype(a: anytype, b: anytype) @TypeOf(a + b) { - return if (a > b) a else b; -} - -fn max_i32(a: i32, b: i32) i32 { - return max_anytype(a, b); -} - -fn max_f64(a: f64, b: f64) f64 { - return max_anytype(a, b); -} - pub fn List(comptime T: type) type { return SmallList(T, 8); } -- cgit v1.2.3 From f81b2531cb4904064446f84a06f6e09e4120e28a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 7 Aug 2021 15:41:52 -0700 Subject: stage2: pass some pointer tests * New AIR instructions: ptr_add, ptr_sub, ptr_elem_val, ptr_ptr_elem_val - See the doc comments for details. * Sema: implement runtime pointer arithmetic. * Sema: implement elem_val for many-pointers. * Sema: support coercion from `*[N:s]T` to `[*]T`. * Type: isIndexable handles many-pointers. --- src/Air.zig | 33 ++++- src/Liveness.zig | 4 + src/Sema.zig | 148 +++++++++++++----- src/codegen.zig | 40 +++-- src/codegen/c.zig | 28 +++- src/codegen/llvm.zig | 60 +++++++- src/codegen/llvm/bindings.zig | 3 + src/print_air.zig | 4 + src/type.zig | 14 +- test/behavior.zig | 3 +- test/behavior/pointers.zig | 301 ------------------------------------- test/behavior/pointers_stage1.zig | 305 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 577 insertions(+), 366 deletions(-) create mode 100644 test/behavior/pointers_stage1.zig (limited to 'src/codegen/llvm.zig') diff --git a/src/Air.zig b/src/Air.zig index d923bf0b02..391683afd5 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -69,6 +69,18 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. div, + /// Add an offset to a pointer, returning a new pointer. + /// The offset is in element type units, not bytes. + /// Wrapping is undefined behavior. + /// The lhs is the pointer, rhs is the offset. Result type is the same as lhs. + /// Uses the `bin_op` field. + ptr_add, + /// Subtract an offset from a pointer, returning a new pointer. + /// The offset is in element type units, not bytes. + /// Wrapping is undefined behavior. + /// The lhs is the pointer, rhs is the offset. Result type is the same as lhs. + /// Uses the `bin_op` field. + ptr_sub, /// Allocates stack local memory. /// Uses the `ty` field. alloc, @@ -264,6 +276,15 @@ pub const Inst = struct { /// Result type is the element type of the slice operand (2 element type operations). /// Uses the `bin_op` field. ptr_slice_elem_val, + /// Given a pointer value, and element index, return the element value at that index. + /// Result type is the element type of the pointer operand. + /// Uses the `bin_op` field. + ptr_elem_val, + /// Given a pointer to a pointer, and element index, return the element value of the inner + /// pointer at that index. + /// Result type is the element type of the inner pointer operand. + /// Uses the `bin_op` field. + ptr_ptr_elem_val, pub fn fromCmpOp(op: std.math.CompareOperator) Tag { return switch (op) { @@ -422,6 +443,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .bit_and, .bit_or, .xor, + .ptr_add, + .ptr_sub, => return air.typeOf(datas[inst].bin_op.lhs), .cmp_lt, @@ -495,14 +518,14 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { return callee_ty.fnReturnType(); }, - .slice_elem_val => { + .slice_elem_val, .ptr_elem_val => { const slice_ty = air.typeOf(datas[inst].bin_op.lhs); return slice_ty.elemType(); }, - .ptr_slice_elem_val => { - const ptr_slice_ty = air.typeOf(datas[inst].bin_op.lhs); - const slice_ty = ptr_slice_ty.elemType(); - return slice_ty.elemType(); + .ptr_slice_elem_val, .ptr_ptr_elem_val => { + const outer_ptr_ty = air.typeOf(datas[inst].bin_op.lhs); + const inner_ptr_ty = outer_ptr_ty.elemType(); + return inner_ptr_ty.elemType(); }, } } diff --git a/src/Liveness.zig b/src/Liveness.zig index 4e22febc5a..48603fc7c9 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -231,6 +231,8 @@ fn analyzeInst( .mul, .mulwrap, .div, + .ptr_add, + .ptr_sub, .bit_and, .bit_or, .xor, @@ -245,6 +247,8 @@ fn analyzeInst( .store, .slice_elem_val, .ptr_slice_elem_val, + .ptr_elem_val, + .ptr_ptr_elem_val, => { const o = inst_datas[inst].bin_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index 6c68ceaf2a..a783a48c64 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5471,6 +5471,41 @@ fn analyzeArithmetic( lhs_ty, rhs_ty, }); } + if (lhs_zig_ty_tag == .Pointer) switch (lhs_ty.ptrSize()) { + .One, .Slice => {}, + .Many, .C => { + // Pointer arithmetic. + const op_src = src; // TODO better source location + const air_tag: Air.Inst.Tag = switch (zir_tag) { + .add => .ptr_add, + .sub => .ptr_sub, + else => return sema.mod.fail( + &block.base, + op_src, + "invalid pointer arithmetic operand: '{s}''", + .{@tagName(zir_tag)}, + ), + }; + // TODO if the operand is comptime-known to be negative, or is a negative int, + // coerce to isize instead of usize. + const casted_rhs = try sema.coerce(block, Type.initTag(.usize), rhs, rhs_src); + const runtime_src = runtime_src: { + if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| { + if (try sema.resolveDefinedValue(block, rhs_src, casted_rhs)) |rhs_val| { + _ = lhs_val; + _ = rhs_val; + return sema.mod.fail(&block.base, src, "TODO implement Sema for comptime pointer arithmetic", .{}); + } else { + break :runtime_src rhs_src; + } + } else { + break :runtime_src lhs_src; + } + }; + try sema.requireRuntimeBlock(block, runtime_src); + return block.addBinOp(air_tag, lhs, casted_rhs); + }, + }; const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; const resolved_type = try sema.resolvePeerTypes(block, src, instructions); @@ -7959,38 +7994,83 @@ fn elemVal( ) CompileError!Air.Inst.Ref { const array_ptr_src = src; // TODO better source location const maybe_ptr_ty = sema.typeOf(array_maybe_ptr); - if (maybe_ptr_ty.isSinglePointer()) { - const indexable_ty = maybe_ptr_ty.elemType(); - if (indexable_ty.isSlice()) { - // We have a pointer to a slice and we want an element value. - if (try sema.isComptimeKnown(block, src, array_maybe_ptr)) { - const slice = try sema.analyzeLoad(block, src, array_maybe_ptr, array_ptr_src); - if (try sema.resolveDefinedValue(block, src, slice)) |slice_val| { + switch (maybe_ptr_ty.zigTypeTag()) { + .Pointer => switch (maybe_ptr_ty.ptrSize()) { + .Slice => { + if (try sema.resolveDefinedValue(block, src, array_maybe_ptr)) |slice_val| { _ = slice_val; return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known slice", .{}); } try sema.requireRuntimeBlock(block, src); - return block.addBinOp(.slice_elem_val, slice, elem_index); - } - try sema.requireRuntimeBlock(block, src); - return block.addBinOp(.ptr_slice_elem_val, array_maybe_ptr, elem_index); - } - } - if (maybe_ptr_ty.isSlice()) { - if (try sema.resolveDefinedValue(block, src, array_maybe_ptr)) |slice_val| { - _ = slice_val; - return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known slice", .{}); - } - try sema.requireRuntimeBlock(block, src); - return block.addBinOp(.slice_elem_val, array_maybe_ptr, elem_index); + return block.addBinOp(.slice_elem_val, array_maybe_ptr, elem_index); + }, + .Many, .C => { + if (try sema.resolveDefinedValue(block, src, array_maybe_ptr)) |ptr_val| { + _ = ptr_val; + return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known pointer", .{}); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.ptr_elem_val, array_maybe_ptr, elem_index); + }, + .One => { + const indexable_ty = maybe_ptr_ty.elemType(); + switch (indexable_ty.zigTypeTag()) { + .Pointer => switch (indexable_ty.ptrSize()) { + .Slice => { + // We have a pointer to a slice and we want an element value. + if (try sema.isComptimeKnown(block, src, array_maybe_ptr)) { + const slice = try sema.analyzeLoad(block, src, array_maybe_ptr, array_ptr_src); + if (try sema.resolveDefinedValue(block, src, slice)) |slice_val| { + _ = slice_val; + return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known slice", .{}); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.slice_elem_val, slice, elem_index); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.ptr_slice_elem_val, array_maybe_ptr, elem_index); + }, + .Many, .C => { + // We have a pointer to a pointer and we want an element value. + if (try sema.isComptimeKnown(block, src, array_maybe_ptr)) { + const ptr = try sema.analyzeLoad(block, src, array_maybe_ptr, array_ptr_src); + if (try sema.resolveDefinedValue(block, src, ptr)) |ptr_val| { + _ = ptr_val; + return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known pointer", .{}); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.ptr_elem_val, ptr, elem_index); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.ptr_ptr_elem_val, array_maybe_ptr, elem_index); + }, + .One => return sema.mod.fail( + &block.base, + array_ptr_src, + "expected pointer, found '{}'", + .{indexable_ty.elemType()}, + ), + }, + .Array => { + const ptr = try sema.elemPtr(block, src, array_maybe_ptr, elem_index, elem_index_src); + return sema.analyzeLoad(block, src, ptr, elem_index_src); + }, + else => return sema.mod.fail( + &block.base, + array_ptr_src, + "expected pointer, found '{}'", + .{indexable_ty}, + ), + } + }, + }, + else => return sema.mod.fail( + &block.base, + array_ptr_src, + "expected pointer, found '{}'", + .{maybe_ptr_ty}, + ), } - - const array_ptr = if (maybe_ptr_ty.zigTypeTag() == .Pointer) - array_maybe_ptr - else - try sema.analyzeRef(block, src, array_maybe_ptr); - const ptr = try sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src); - return sema.analyzeLoad(block, src, ptr, elem_index_src); } fn elemPtrArray( @@ -8107,17 +8187,15 @@ fn coerce( .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 sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src); - - if (src_sentinel) |src_s| { - if (dst_sentinel) |dst_s| { - if (src_s.eql(dst_s, dst_elem_type)) { + // *[N:s]T to [*]T + if (dest_type.sentinel()) |dst_sentinel| { + if (array_type.sentinel()) |src_sentinel| { + if (src_sentinel.eql(dst_sentinel, dst_elem_type)) { return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src); } } + } else { + return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src); } }, .One => {}, diff --git a/src/codegen.zig b/src/codegen.zig index 7fd51d3cd8..f5cdc518f6 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -802,13 +802,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (air_tags[inst]) { // zig fmt: off - .add => try self.airAdd(inst), - .addwrap => try self.airAddWrap(inst), - .sub => try self.airSub(inst), - .subwrap => try self.airSubWrap(inst), - .mul => try self.airMul(inst), - .mulwrap => try self.airMulWrap(inst), - .div => try self.airDiv(inst), + .add, .ptr_add => try self.airAdd(inst), + .addwrap => try self.airAddWrap(inst), + .sub, .ptr_sub => try self.airSub(inst), + .subwrap => try self.airSubWrap(inst), + .mul => try self.airMul(inst), + .mulwrap => try self.airMulWrap(inst), + .div => try self.airDiv(inst), .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), @@ -859,6 +859,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), + .ptr_elem_val => try self.airPtrElemVal(inst), + .ptr_ptr_elem_val => try self.airPtrPtrElemVal(inst), .constant => unreachable, // excluded from function bodies .const_ty => unreachable, // excluded from function bodies @@ -1369,21 +1371,41 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { + const is_volatile = false; // TODO const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) { else => return self.fail("TODO implement slice_elem_val for {}", .{self.target.cpu.arch}), }; return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } fn airPtrSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { + const is_volatile = false; // TODO const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) { else => return self.fail("TODO implement ptr_slice_elem_val for {}", .{self.target.cpu.arch}), }; return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { + const is_volatile = false; // TODO + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement ptr_elem_val for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + + fn airPtrPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { + const is_volatile = false; // TODO + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement ptr_ptr_elem_val for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool { if (!self.liveness.operandDies(inst, op_index)) return false; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 22420aca45..65ad4bac8e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -850,19 +850,19 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM // TODO use a different strategy for add that communicates to the optimizer // that wrapping is UB. - .add => try airBinOp( o, inst, " + "), - .addwrap => try airWrapOp(o, inst, " + ", "addw_"), + .add, .ptr_add => try airBinOp( o, inst, " + "), + .addwrap => try airWrapOp(o, inst, " + ", "addw_"), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. - .sub => try airBinOp( o, inst, " - "), - .subwrap => try airWrapOp(o, inst, " - ", "subw_"), + .sub, .ptr_sub => try airBinOp( o, inst, " - "), + .subwrap => try airWrapOp(o, inst, " - ", "subw_"), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. - .mul => try airBinOp( o, inst, " * "), - .mulwrap => try airWrapOp(o, inst, " * ", "mulw_"), + .mul => try airBinOp( o, inst, " * "), + .mulwrap => try airWrapOp(o, inst, " * ", "mulw_"), // TODO use a different strategy for div that communicates to the optimizer // that wrapping is UB. - .div => try airBinOp( o, inst, " / "), + .div => try airBinOp( o, inst, " / "), .cmp_eq => try airBinOp(o, inst, " == "), .cmp_gt => try airBinOp(o, inst, " > "), @@ -915,6 +915,8 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .slice_ptr => try airSliceField(o, inst, ".ptr;\n"), .slice_len => try airSliceField(o, inst, ".len;\n"), + .ptr_elem_val => try airPtrElemVal(o, inst, "["), + .ptr_ptr_elem_val => try airPtrElemVal(o, inst, "[0]["), .slice_elem_val => try airSliceElemVal(o, inst, "["), .ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["), @@ -953,8 +955,18 @@ fn airSliceField(o: *Object, inst: Air.Inst.Index, suffix: []const u8) !CValue { return local; } +fn airPtrElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { + const is_volatile = false; // TODO + if (!is_volatile and o.liveness.isUnused(inst)) + return CValue.none; + + _ = prefix; + return o.dg.fail("TODO: C backend: airPtrElemVal", .{}); +} + fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { - if (o.liveness.isUnused(inst)) + const is_volatile = false; // TODO + if (!is_volatile and o.liveness.isUnused(inst)) return CValue.none; const bin_op = o.air.instructions.items(.data)[inst].bin_op; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4b7454d878..eccd5fa04f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -966,6 +966,8 @@ pub const FuncGen = struct { .mul => try self.airMul(inst, false), .mulwrap => try self.airMul(inst, true), .div => try self.airDiv(inst), + .ptr_add => try self.airPtrAdd(inst), + .ptr_sub => try self.airPtrSub(inst), .bit_and, .bool_and => try self.airAnd(inst), .bit_or, .bool_or => try self.airOr(inst), @@ -1015,6 +1017,8 @@ pub const FuncGen = struct { .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), + .ptr_elem_val => try self.airPtrElemVal(inst), + .ptr_ptr_elem_val => try self.airPtrPtrElemVal(inst), .optional_payload => try self.airOptionalPayload(inst, false), .optional_payload_ptr => try self.airOptionalPayload(inst, true), @@ -1229,7 +1233,8 @@ pub const FuncGen = struct { } fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) + const is_volatile = false; // TODO + if (!is_volatile and self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; @@ -1242,7 +1247,8 @@ pub const FuncGen = struct { } fn airPtrSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) + const is_volatile = false; // TODO + if (!is_volatile and self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; @@ -1264,6 +1270,33 @@ pub const FuncGen = struct { return self.builder.buildLoad(ptr, ""); } + fn airPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const is_volatile = false; // TODO + if (!is_volatile and self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const base_ptr = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const indices: [1]*const llvm.Value = .{rhs}; + const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + return self.builder.buildLoad(ptr, ""); + } + + fn airPtrPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const is_volatile = false; // TODO + if (!is_volatile and self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const base_ptr = self.builder.buildLoad(lhs, ""); + const indices: [1]*const llvm.Value = .{rhs}; + const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + return self.builder.buildLoad(ptr, ""); + } + fn airStructFieldPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1624,6 +1657,29 @@ pub const FuncGen = struct { return self.builder.buildUDiv(lhs, rhs, ""); } + fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const base_ptr = try self.resolveInst(bin_op.lhs); + const offset = try self.resolveInst(bin_op.rhs); + const indices: [1]*const llvm.Value = .{offset}; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } + + fn airPtrSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const base_ptr = try self.resolveInst(bin_op.lhs); + const offset = try self.resolveInst(bin_op.rhs); + const negative_offset = self.builder.buildNeg(offset, ""); + const indices: [1]*const llvm.Value = .{negative_offset}; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } + fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 096cd4a5b4..4bb8a4a18b 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -324,6 +324,9 @@ pub const Builder = opaque { pub const buildLoad = LLVMBuildLoad; extern fn LLVMBuildLoad(*const Builder, PointerVal: *const Value, Name: [*:0]const u8) *const Value; + pub const buildNeg = LLVMBuildNeg; + extern fn LLVMBuildNeg(*const Builder, V: *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; diff --git a/src/print_air.zig b/src/print_air.zig index 11f2982fc3..66490b6512 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -109,6 +109,8 @@ const Writer = struct { .mul, .mulwrap, .div, + .ptr_add, + .ptr_sub, .bit_and, .bit_or, .xor, @@ -123,6 +125,8 @@ const Writer = struct { .store, .slice_elem_val, .ptr_slice_elem_val, + .ptr_elem_val, + .ptr_ptr_elem_val, => try w.writeBinOp(s, inst), .is_null, diff --git a/src/type.zig b/src/type.zig index 02b9fabe71..28b87a8afe 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2753,11 +2753,15 @@ pub const Type = extern union { }; } - pub fn isIndexable(self: Type) bool { - const zig_tag = self.zigTypeTag(); - // TODO tuples are indexable - return zig_tag == .Array or zig_tag == .Vector or self.isSlice() or - (self.isSinglePointer() and self.elemType().zigTypeTag() == .Array); + pub fn isIndexable(ty: Type) bool { + return switch (ty.zigTypeTag()) { + .Array, .Vector => true, + .Pointer => switch (ty.ptrSize()) { + .Slice, .Many, .C => true, + .One => ty.elemType().zigTypeTag() == .Array, + }, + else => false, // TODO tuples are indexable + }; } /// Returns null if the type has no namespace. diff --git a/test/behavior.zig b/test/behavior.zig index f1c6fa3a35..936268af9c 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -6,6 +6,7 @@ test { _ = @import("behavior/basic.zig"); _ = @import("behavior/generics.zig"); _ = @import("behavior/eval.zig"); + _ = @import("behavior/pointers.zig"); if (!builtin.zig_is_stage2) { // Tests that only pass for stage1. @@ -112,7 +113,7 @@ test { _ = @import("behavior/namespace_depends_on_compile_var.zig"); _ = @import("behavior/null.zig"); _ = @import("behavior/optional.zig"); - _ = @import("behavior/pointers.zig"); + _ = @import("behavior/pointers_stage1.zig"); _ = @import("behavior/popcount.zig"); _ = @import("behavior/ptrcast.zig"); _ = @import("behavior/pub_enum.zig"); diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index bb95d3c219..4fcd78b1d6 100644 --- a/test/behavior/pointers.zig +++ b/test/behavior/pointers.zig @@ -15,22 +15,6 @@ fn testDerefPtr() !void { try expect(x == 1235); } -const Foo1 = struct { - x: void, -}; - -test "dereference pointer again" { - try testDerefPtrOneVal(); - comptime try testDerefPtrOneVal(); -} - -fn testDerefPtrOneVal() !void { - // Foo1 satisfies the OnePossibleValueYes criteria - const x = &Foo1{ .x = {} }; - const y = x.*; - try expect(@TypeOf(y.x) == void); -} - test "pointer arithmetic" { var ptr: [*]const u8 = "abcd"; @@ -60,288 +44,3 @@ test "double pointer parsing" { fn PtrOf(comptime T: type) type { return *T; } - -test "assigning integer to C pointer" { - var x: i32 = 0; - var ptr: [*c]u8 = 0; - var ptr2: [*c]u8 = x; - if (false) { - ptr; - ptr2; - } -} - -test "implicit cast single item pointer to C pointer and back" { - var y: u8 = 11; - var x: [*c]u8 = &y; - var z: *u8 = x; - z.* += 1; - try expect(y == 12); -} - -test "C pointer comparison and arithmetic" { - const S = struct { - fn doTheTest() !void { - var ptr1: [*c]u32 = 0; - var ptr2 = ptr1 + 10; - try expect(ptr1 == 0); - try expect(ptr1 >= 0); - try expect(ptr1 <= 0); - // expect(ptr1 < 1); - // expect(ptr1 < one); - // expect(1 > ptr1); - // expect(one > ptr1); - try expect(ptr1 < ptr2); - try expect(ptr2 > ptr1); - try expect(ptr2 >= 40); - try expect(ptr2 == 40); - try expect(ptr2 <= 40); - ptr2 -= 10; - try expect(ptr1 == ptr2); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "peer type resolution with C pointers" { - var ptr_one: *u8 = undefined; - var ptr_many: [*]u8 = undefined; - var ptr_c: [*c]u8 = undefined; - var t = true; - var x1 = if (t) ptr_one else ptr_c; - var x2 = if (t) ptr_many else ptr_c; - var x3 = if (t) ptr_c else ptr_one; - var x4 = if (t) ptr_c else ptr_many; - try expect(@TypeOf(x1) == [*c]u8); - try expect(@TypeOf(x2) == [*c]u8); - try expect(@TypeOf(x3) == [*c]u8); - try expect(@TypeOf(x4) == [*c]u8); -} - -test "implicit casting between C pointer and optional non-C pointer" { - var slice: []const u8 = "aoeu"; - const opt_many_ptr: ?[*]const u8 = slice.ptr; - var ptr_opt_many_ptr = &opt_many_ptr; - var c_ptr: [*c]const [*c]const u8 = ptr_opt_many_ptr; - try expect(c_ptr.*.* == 'a'); - ptr_opt_many_ptr = c_ptr; - try expect(ptr_opt_many_ptr.*.?[1] == 'o'); -} - -test "implicit cast error unions with non-optional to optional pointer" { - const S = struct { - fn doTheTest() !void { - try expectError(error.Fail, foo()); - } - fn foo() anyerror!?*u8 { - return bar() orelse error.Fail; - } - fn bar() ?*u8 { - return null; - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "initialize const optional C pointer to null" { - const a: ?[*c]i32 = null; - try expect(a == null); - comptime try expect(a == null); -} - -test "compare equality of optional and non-optional pointer" { - const a = @intToPtr(*const usize, 0x12345678); - const b = @intToPtr(?*usize, 0x12345678); - try expect(a == b); - try expect(b == a); -} - -test "allowzero pointer and slice" { - var ptr = @intToPtr([*]allowzero i32, 0); - var opt_ptr: ?[*]allowzero i32 = ptr; - try expect(opt_ptr != null); - try expect(@ptrToInt(ptr) == 0); - var runtime_zero: usize = 0; - var slice = ptr[runtime_zero..10]; - comptime try expect(@TypeOf(slice) == []allowzero i32); - try expect(@ptrToInt(&slice[5]) == 20); - - comptime try expect(@typeInfo(@TypeOf(ptr)).Pointer.is_allowzero); - comptime try expect(@typeInfo(@TypeOf(slice)).Pointer.is_allowzero); -} - -test "assign null directly to C pointer and test null equality" { - var x: [*c]i32 = null; - try expect(x == null); - try expect(null == x); - try expect(!(x != null)); - try expect(!(null != x)); - if (x) |same_x| { - _ = same_x; - @panic("fail"); - } - var otherx: i32 = undefined; - try expect((x orelse &otherx) == &otherx); - - const y: [*c]i32 = null; - comptime try expect(y == null); - comptime try expect(null == y); - comptime try expect(!(y != null)); - comptime try expect(!(null != y)); - if (y) |same_y| { - _ = same_y; - @panic("fail"); - } - const othery: i32 = undefined; - comptime try expect((y orelse &othery) == &othery); - - var n: i32 = 1234; - var x1: [*c]i32 = &n; - try expect(!(x1 == null)); - try expect(!(null == x1)); - try expect(x1 != null); - try expect(null != x1); - try expect(x1.?.* == 1234); - if (x1) |same_x1| { - try expect(same_x1.* == 1234); - } else { - @panic("fail"); - } - try expect((x1 orelse &otherx) == x1); - - const nc: i32 = 1234; - const y1: [*c]const i32 = &nc; - comptime try expect(!(y1 == null)); - comptime try expect(!(null == y1)); - comptime try expect(y1 != null); - comptime try expect(null != y1); - comptime try expect(y1.?.* == 1234); - if (y1) |same_y1| { - try expect(same_y1.* == 1234); - } else { - @compileError("fail"); - } - comptime try expect((y1 orelse &othery) == y1); -} - -test "null terminated pointer" { - const S = struct { - fn doTheTest() !void { - var array_with_zero = [_:0]u8{ 'h', 'e', 'l', 'l', 'o' }; - var zero_ptr: [*:0]const u8 = @ptrCast([*:0]const u8, &array_with_zero); - var no_zero_ptr: [*]const u8 = zero_ptr; - var zero_ptr_again = @ptrCast([*:0]const u8, no_zero_ptr); - try expect(std.mem.eql(u8, std.mem.spanZ(zero_ptr_again), "hello")); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "allow any sentinel" { - const S = struct { - fn doTheTest() !void { - var array = [_:std.math.minInt(i32)]i32{ 1, 2, 3, 4 }; - var ptr: [*:std.math.minInt(i32)]i32 = &array; - try expect(ptr[4] == std.math.minInt(i32)); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "pointer sentinel with enums" { - const S = struct { - const Number = enum { - one, - two, - sentinel, - }; - - fn doTheTest() !void { - var ptr: [*:.sentinel]const Number = &[_:.sentinel]Number{ .one, .two, .two, .one }; - try expect(ptr[4] == .sentinel); // TODO this should be comptime try expect, see #3731 - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "pointer sentinel with optional element" { - const S = struct { - fn doTheTest() !void { - var ptr: [*:null]const ?i32 = &[_:null]?i32{ 1, 2, 3, 4 }; - try expect(ptr[4] == null); // TODO this should be comptime try expect, see #3731 - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "pointer sentinel with +inf" { - const S = struct { - fn doTheTest() !void { - const inf = std.math.inf_f32; - var ptr: [*:inf]const f32 = &[_:inf]f32{ 1.1, 2.2, 3.3, 4.4 }; - try expect(ptr[4] == inf); // TODO this should be comptime try expect, see #3731 - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "pointer to array at fixed address" { - const array = @intToPtr(*volatile [1]u32, 0x10); - // Silly check just to reference `array` - try expect(@ptrToInt(&array[0]) == 0x10); -} - -test "pointer arithmetic affects the alignment" { - { - var ptr: [*]align(8) u32 = undefined; - var x: usize = 1; - - try expect(@typeInfo(@TypeOf(ptr)).Pointer.alignment == 8); - const ptr1 = ptr + 1; // 1 * 4 = 4 -> lcd(4,8) = 4 - try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 4); - const ptr2 = ptr + 4; // 4 * 4 = 16 -> lcd(16,8) = 8 - try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 8); - const ptr3 = ptr + 0; // no-op - try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); - const ptr4 = ptr + x; // runtime-known addend - try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); - } - { - var ptr: [*]align(8) [3]u8 = undefined; - var x: usize = 1; - - const ptr1 = ptr + 17; // 3 * 17 = 51 - try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 1); - const ptr2 = ptr + x; // runtime-known addend - try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 1); - const ptr3 = ptr + 8; // 3 * 8 = 24 -> lcd(8,24) = 8 - try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); - const ptr4 = ptr + 4; // 3 * 4 = 12 -> lcd(8,12) = 4 - try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); - } -} - -test "@ptrToInt on null optional at comptime" { - { - const pointer = @intToPtr(?*u8, 0x000); - const x = @ptrToInt(pointer); - _ = x; - comptime try expect(0 == @ptrToInt(pointer)); - } - { - const pointer = @intToPtr(?*u8, 0xf00); - comptime try expect(0xf00 == @ptrToInt(pointer)); - } -} - -test "indexing array with sentinel returns correct type" { - var s: [:0]const u8 = "abc"; - try testing.expectEqualSlices(u8, "*const u8", @typeName(@TypeOf(&s[0]))); -} diff --git a/test/behavior/pointers_stage1.zig b/test/behavior/pointers_stage1.zig new file mode 100644 index 0000000000..aea123a5c3 --- /dev/null +++ b/test/behavior/pointers_stage1.zig @@ -0,0 +1,305 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectError = testing.expectError; + +const Foo1 = struct { + x: void, +}; + +test "dereference pointer again" { + try testDerefPtrOneVal(); + comptime try testDerefPtrOneVal(); +} + +fn testDerefPtrOneVal() !void { + // Foo1 satisfies the OnePossibleValueYes criteria + const x = &Foo1{ .x = {} }; + const y = x.*; + try expect(@TypeOf(y.x) == void); +} + +test "assigning integer to C pointer" { + var x: i32 = 0; + var ptr: [*c]u8 = 0; + var ptr2: [*c]u8 = x; + if (false) { + ptr; + ptr2; + } +} + +test "implicit cast single item pointer to C pointer and back" { + var y: u8 = 11; + var x: [*c]u8 = &y; + var z: *u8 = x; + z.* += 1; + try expect(y == 12); +} + +test "C pointer comparison and arithmetic" { + const S = struct { + fn doTheTest() !void { + var ptr1: [*c]u32 = 0; + var ptr2 = ptr1 + 10; + try expect(ptr1 == 0); + try expect(ptr1 >= 0); + try expect(ptr1 <= 0); + // expect(ptr1 < 1); + // expect(ptr1 < one); + // expect(1 > ptr1); + // expect(one > ptr1); + try expect(ptr1 < ptr2); + try expect(ptr2 > ptr1); + try expect(ptr2 >= 40); + try expect(ptr2 == 40); + try expect(ptr2 <= 40); + ptr2 -= 10; + try expect(ptr1 == ptr2); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "peer type resolution with C pointers" { + var ptr_one: *u8 = undefined; + var ptr_many: [*]u8 = undefined; + var ptr_c: [*c]u8 = undefined; + var t = true; + var x1 = if (t) ptr_one else ptr_c; + var x2 = if (t) ptr_many else ptr_c; + var x3 = if (t) ptr_c else ptr_one; + var x4 = if (t) ptr_c else ptr_many; + try expect(@TypeOf(x1) == [*c]u8); + try expect(@TypeOf(x2) == [*c]u8); + try expect(@TypeOf(x3) == [*c]u8); + try expect(@TypeOf(x4) == [*c]u8); +} + +test "implicit casting between C pointer and optional non-C pointer" { + var slice: []const u8 = "aoeu"; + const opt_many_ptr: ?[*]const u8 = slice.ptr; + var ptr_opt_many_ptr = &opt_many_ptr; + var c_ptr: [*c]const [*c]const u8 = ptr_opt_many_ptr; + try expect(c_ptr.*.* == 'a'); + ptr_opt_many_ptr = c_ptr; + try expect(ptr_opt_many_ptr.*.?[1] == 'o'); +} + +test "implicit cast error unions with non-optional to optional pointer" { + const S = struct { + fn doTheTest() !void { + try expectError(error.Fail, foo()); + } + fn foo() anyerror!?*u8 { + return bar() orelse error.Fail; + } + fn bar() ?*u8 { + return null; + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "initialize const optional C pointer to null" { + const a: ?[*c]i32 = null; + try expect(a == null); + comptime try expect(a == null); +} + +test "compare equality of optional and non-optional pointer" { + const a = @intToPtr(*const usize, 0x12345678); + const b = @intToPtr(?*usize, 0x12345678); + try expect(a == b); + try expect(b == a); +} + +test "allowzero pointer and slice" { + var ptr = @intToPtr([*]allowzero i32, 0); + var opt_ptr: ?[*]allowzero i32 = ptr; + try expect(opt_ptr != null); + try expect(@ptrToInt(ptr) == 0); + var runtime_zero: usize = 0; + var slice = ptr[runtime_zero..10]; + comptime try expect(@TypeOf(slice) == []allowzero i32); + try expect(@ptrToInt(&slice[5]) == 20); + + comptime try expect(@typeInfo(@TypeOf(ptr)).Pointer.is_allowzero); + comptime try expect(@typeInfo(@TypeOf(slice)).Pointer.is_allowzero); +} + +test "assign null directly to C pointer and test null equality" { + var x: [*c]i32 = null; + try expect(x == null); + try expect(null == x); + try expect(!(x != null)); + try expect(!(null != x)); + if (x) |same_x| { + _ = same_x; + @panic("fail"); + } + var otherx: i32 = undefined; + try expect((x orelse &otherx) == &otherx); + + const y: [*c]i32 = null; + comptime try expect(y == null); + comptime try expect(null == y); + comptime try expect(!(y != null)); + comptime try expect(!(null != y)); + if (y) |same_y| { + _ = same_y; + @panic("fail"); + } + const othery: i32 = undefined; + comptime try expect((y orelse &othery) == &othery); + + var n: i32 = 1234; + var x1: [*c]i32 = &n; + try expect(!(x1 == null)); + try expect(!(null == x1)); + try expect(x1 != null); + try expect(null != x1); + try expect(x1.?.* == 1234); + if (x1) |same_x1| { + try expect(same_x1.* == 1234); + } else { + @panic("fail"); + } + try expect((x1 orelse &otherx) == x1); + + const nc: i32 = 1234; + const y1: [*c]const i32 = &nc; + comptime try expect(!(y1 == null)); + comptime try expect(!(null == y1)); + comptime try expect(y1 != null); + comptime try expect(null != y1); + comptime try expect(y1.?.* == 1234); + if (y1) |same_y1| { + try expect(same_y1.* == 1234); + } else { + @compileError("fail"); + } + comptime try expect((y1 orelse &othery) == y1); +} + +test "null terminated pointer" { + const S = struct { + fn doTheTest() !void { + var array_with_zero = [_:0]u8{ 'h', 'e', 'l', 'l', 'o' }; + var zero_ptr: [*:0]const u8 = @ptrCast([*:0]const u8, &array_with_zero); + var no_zero_ptr: [*]const u8 = zero_ptr; + var zero_ptr_again = @ptrCast([*:0]const u8, no_zero_ptr); + try expect(std.mem.eql(u8, std.mem.spanZ(zero_ptr_again), "hello")); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "allow any sentinel" { + const S = struct { + fn doTheTest() !void { + var array = [_:std.math.minInt(i32)]i32{ 1, 2, 3, 4 }; + var ptr: [*:std.math.minInt(i32)]i32 = &array; + try expect(ptr[4] == std.math.minInt(i32)); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "pointer sentinel with enums" { + const S = struct { + const Number = enum { + one, + two, + sentinel, + }; + + fn doTheTest() !void { + var ptr: [*:.sentinel]const Number = &[_:.sentinel]Number{ .one, .two, .two, .one }; + try expect(ptr[4] == .sentinel); // TODO this should be comptime try expect, see #3731 + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "pointer sentinel with optional element" { + const S = struct { + fn doTheTest() !void { + var ptr: [*:null]const ?i32 = &[_:null]?i32{ 1, 2, 3, 4 }; + try expect(ptr[4] == null); // TODO this should be comptime try expect, see #3731 + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "pointer sentinel with +inf" { + const S = struct { + fn doTheTest() !void { + const inf = std.math.inf_f32; + var ptr: [*:inf]const f32 = &[_:inf]f32{ 1.1, 2.2, 3.3, 4.4 }; + try expect(ptr[4] == inf); // TODO this should be comptime try expect, see #3731 + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "pointer to array at fixed address" { + const array = @intToPtr(*volatile [1]u32, 0x10); + // Silly check just to reference `array` + try expect(@ptrToInt(&array[0]) == 0x10); +} + +test "pointer arithmetic affects the alignment" { + { + var ptr: [*]align(8) u32 = undefined; + var x: usize = 1; + + try expect(@typeInfo(@TypeOf(ptr)).Pointer.alignment == 8); + const ptr1 = ptr + 1; // 1 * 4 = 4 -> lcd(4,8) = 4 + try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 4); + const ptr2 = ptr + 4; // 4 * 4 = 16 -> lcd(16,8) = 8 + try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 8); + const ptr3 = ptr + 0; // no-op + try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); + const ptr4 = ptr + x; // runtime-known addend + try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); + } + { + var ptr: [*]align(8) [3]u8 = undefined; + var x: usize = 1; + + const ptr1 = ptr + 17; // 3 * 17 = 51 + try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 1); + const ptr2 = ptr + x; // runtime-known addend + try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 1); + const ptr3 = ptr + 8; // 3 * 8 = 24 -> lcd(8,24) = 8 + try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); + const ptr4 = ptr + 4; // 3 * 4 = 12 -> lcd(8,12) = 4 + try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); + } +} + +test "@ptrToInt on null optional at comptime" { + { + const pointer = @intToPtr(?*u8, 0x000); + const x = @ptrToInt(pointer); + _ = x; + comptime try expect(0 == @ptrToInt(pointer)); + } + { + const pointer = @intToPtr(?*u8, 0xf00); + comptime try expect(0xf00 == @ptrToInt(pointer)); + } +} + +test "indexing array with sentinel returns correct type" { + var s: [:0]const u8 = "abc"; + try testing.expectEqualSlices(u8, "*const u8", @typeName(@TypeOf(&s[0]))); +} -- cgit v1.2.3 From 799fedf612aa8742c446b015c12d21707a1dbec0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 7 Aug 2021 20:34:28 -0700 Subject: stage2: pass some error union tests * Value: rename `error_union` to `eu_payload` and clarify the intended usage in the doc comments. The way error unions is represented with Value is fixed to not have ambiguous values. * Fix codegen for error union constants in all the backends. * Implement the AIR instructions having to do with error unions in the LLVM backend. --- src/Sema.zig | 16 ++++------ src/codegen.zig | 2 +- src/codegen/c.zig | 25 ++++++--------- src/codegen/llvm.zig | 78 +++++++++++++++++++++++++++++---------------- src/codegen/wasm.zig | 18 +++++------ src/value.zig | 43 ++++++++++++++++++------- test/behavior.zig | 3 +- test/behavior/if.zig | 42 ------------------------ test/behavior/if_stage1.zig | 45 ++++++++++++++++++++++++++ 9 files changed, 155 insertions(+), 117 deletions(-) create mode 100644 test/behavior/if_stage1.zig (limited to 'src/codegen/llvm.zig') diff --git a/src/Sema.zig b/src/Sema.zig index a783a48c64..96a09553f5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3468,7 +3468,7 @@ fn zirErrUnionPayload( if (val.getError()) |name| { return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); } - const data = val.castTag(.error_union).?.data; + const data = val.castTag(.eu_payload).?.data; const result_ty = operand_ty.errorUnionPayload(); return sema.addConstant(result_ty, data); } @@ -3539,8 +3539,7 @@ fn zirErrUnionCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compi if (try sema.resolveDefinedValue(block, src, operand)) |val| { assert(val.getError() != null); - const data = val.castTag(.error_union).?.data; - return sema.addConstant(result_ty, data); + return sema.addConstant(result_ty, val); } try sema.requireRuntimeBlock(block, src); @@ -3566,8 +3565,7 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { if (try pointer_val.pointerDeref(sema.arena)) |val| { assert(val.getError() != null); - const data = val.castTag(.error_union).?.data; - return sema.addConstant(result_ty, data); + return sema.addConstant(result_ty, val); } } @@ -8900,7 +8898,9 @@ fn wrapErrorUnion( if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { if (inst_ty.zigTypeTag() != .ErrorSet) { _ = try sema.coerce(block, dest_payload_ty, inst, inst_src); - } else switch (dest_err_set_ty.tag()) { + return sema.addConstant(dest_type, try Value.Tag.eu_payload.create(sema.arena, val)); + } + switch (dest_err_set_ty.tag()) { .anyerror => {}, .error_set_single => { const expected_name = val.castTag(.@"error").?.data.name; @@ -8946,9 +8946,7 @@ fn wrapErrorUnion( }, else => unreachable, } - - // Create a SubValue for the error_union payload. - return sema.addConstant(dest_type, try Value.Tag.error_union.create(sema.arena, val)); + return sema.addConstant(dest_type, val); } try sema.requireRuntimeBlock(block, inst_src); diff --git a/src/codegen.zig b/src/codegen.zig index f5cdc518f6..38cc27d5bc 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -4815,7 +4815,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .ErrorUnion => { const error_type = typed_value.ty.errorUnionSet(); const payload_type = typed_value.ty.errorUnionPayload(); - const sub_val = typed_value.val.castTag(.error_union).?.data; + const sub_val = typed_value.val.castTag(.eu_payload).?.data; if (!payload_type.hasCodeGenBits()) { // We use the error type directly as the type. diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 65ad4bac8e..a67e2438c2 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -350,32 +350,25 @@ pub const DeclGen = struct { .ErrorUnion => { const error_type = t.errorUnionSet(); const payload_type = t.errorUnionPayload(); - const sub_val = val.castTag(.error_union).?.data; if (!payload_type.hasCodeGenBits()) { // We use the error type directly as the type. - return dg.renderValue(writer, error_type, sub_val); + const err_val = if (val.errorUnionIsPayload()) Value.initTag(.zero) else val; + return dg.renderValue(writer, error_type, err_val); } try writer.writeByte('('); try dg.renderType(writer, t); try writer.writeAll("){"); - if (val.getError()) |_| { - try writer.writeAll(" .error = "); - try dg.renderValue( - writer, - error_type, - sub_val, - ); - try writer.writeAll(" }"); - } else { + if (val.castTag(.eu_payload)) |pl| { + const payload_val = pl.data; try writer.writeAll(" .payload = "); - try dg.renderValue( - writer, - payload_type, - sub_val, - ); + try dg.renderValue(writer, payload_type, payload_val); try writer.writeAll(", .error = 0 }"); + } else { + try writer.writeAll(" .error = "); + try dg.renderValue(writer, error_type, val); + try writer.writeAll(" }"); } }, .Enum => { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index eccd5fa04f..7cfbc8da5e 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -593,7 +593,7 @@ pub const DeclGen = struct { try self.llvmType(ptr_type), try self.llvmType(Type.initTag(.usize)), }; - return self.context.structType(&fields, 2, .False); + return self.context.structType(&fields, fields.len, .False); } else { const elem_type = try self.llvmType(t.elemType()); return elem_type.pointerType(0); @@ -621,10 +621,14 @@ pub const DeclGen = struct { .ErrorUnion => { const error_type = t.errorUnionSet(); const payload_type = t.errorUnionPayload(); + const llvm_error_type = try self.llvmType(error_type); if (!payload_type.hasCodeGenBits()) { - return self.llvmType(error_type); + return llvm_error_type; } - return self.todo("implement llvmType for error unions", .{}); + const llvm_payload_type = try self.llvmType(payload_type); + + const fields: [2]*const llvm.Type = .{ llvm_error_type, llvm_payload_type }; + return self.context.structType(&fields, fields.len, .False); }, .ErrorSet => { return self.context.intType(16); @@ -846,14 +850,25 @@ pub const DeclGen = struct { .ErrorUnion => { const error_type = tv.ty.errorUnionSet(); const payload_type = tv.ty.errorUnionPayload(); - const sub_val = tv.val.castTag(.error_union).?.data; + const is_pl = tv.val.errorUnionIsPayload(); if (!payload_type.hasCodeGenBits()) { // We use the error type directly as the type. - return self.genTypedValue(.{ .ty = error_type, .val = sub_val }); + const err_val = if (!is_pl) tv.val else Value.initTag(.zero); + return self.genTypedValue(.{ .ty = error_type, .val = err_val }); } - return self.todo("implement error union const of type '{}'", .{tv.ty}); + const fields: [2]*const llvm.Value = .{ + try self.genTypedValue(.{ + .ty = error_type, + .val = if (is_pl) Value.initTag(.zero) else tv.val, + }), + try self.genTypedValue(.{ + .ty = payload_type, + .val = if (tv.val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef), + }), + }; + return self.context.constStruct(&fields, fields.len, .False); }, .Struct => { const fields_len = tv.ty.structFieldCount(); @@ -984,10 +999,10 @@ pub const FuncGen = struct { .is_non_null_ptr => try self.airIsNonNull(inst, true), .is_null => try self.airIsNull(inst, false), .is_null_ptr => try self.airIsNull(inst, true), - .is_non_err => try self.airIsErr(inst, true, false), - .is_non_err_ptr => try self.airIsErr(inst, true, true), - .is_err => try self.airIsErr(inst, false, false), - .is_err_ptr => try self.airIsErr(inst, false, true), + .is_non_err => try self.airIsErr(inst, .EQ, false), + .is_non_err_ptr => try self.airIsErr(inst, .EQ, true), + .is_err => try self.airIsErr(inst, .NE, false), + .is_err_ptr => try self.airIsErr(inst, .NE, true), .alloc => try self.airAlloc(inst), .arg => try self.airArg(inst), @@ -1098,7 +1113,7 @@ pub const FuncGen = struct { const inst_ty = self.air.typeOfIndex(inst); switch (self.air.typeOf(bin_op.lhs).zigTypeTag()) { - .Int, .Bool, .Pointer => { + .Int, .Bool, .Pointer, .ErrorSet => { const is_signed = inst_ty.isSignedInt(); const operation = switch (op) { .eq => .EQ, @@ -1256,12 +1271,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const base_ptr = ptr: { - const index_type = self.context.intType(32); - const indices: [2]*const llvm.Value = .{ - index_type.constNull(), - index_type.constInt(0, .False), - }; - const ptr_field_ptr = self.builder.buildInBoundsGEP(lhs, &indices, 2, ""); + const ptr_field_ptr = self.builder.buildStructGEP(lhs, 0, ""); break :ptr self.builder.buildLoad(ptr_field_ptr, ""); }; @@ -1472,7 +1482,7 @@ pub const FuncGen = struct { index_type.constInt(1, .False), }; - return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, 2, ""), ""); + return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""), ""); } else { return self.builder.buildExtractValue(operand, 1, ""); } @@ -1488,7 +1498,7 @@ pub const FuncGen = struct { fn airIsErr( self: *FuncGen, inst: Air.Inst.Index, - invert_logic: bool, + op: llvm.IntPredicate, operand_is_ptr: bool, ) !?*const llvm.Value { if (self.liveness.isUnused(inst)) @@ -1498,16 +1508,22 @@ pub const FuncGen = struct { const operand = try self.resolveInst(un_op); const err_union_ty = self.air.typeOf(un_op); const payload_ty = err_union_ty.errorUnionPayload(); + const err_set_ty = try self.dg.llvmType(Type.initTag(.anyerror)); + const zero = err_set_ty.constNull(); if (!payload_ty.hasCodeGenBits()) { const loaded = if (operand_is_ptr) self.builder.buildLoad(operand, "") else operand; - const op: llvm.IntPredicate = if (invert_logic) .EQ else .NE; - const err_set_ty = try self.dg.llvmType(Type.initTag(.anyerror)); - const zero = err_set_ty.constNull(); return self.builder.buildICmp(op, loaded, zero, ""); } - return self.todo("implement 'airIsErr' for error unions with nonzero payload", .{}); + if (operand_is_ptr) { + const err_field_ptr = self.builder.buildStructGEP(operand, 0, ""); + const loaded = self.builder.buildLoad(err_field_ptr, ""); + return self.builder.buildICmp(op, loaded, zero, ""); + } + + const loaded = self.builder.buildExtractValue(operand, 0, ""); + return self.builder.buildICmp(op, loaded, zero, ""); } fn airOptionalPayload( @@ -1552,9 +1568,11 @@ pub const FuncGen = struct { return null; } - _ = operand; - _ = operand_is_ptr; - return self.todo("implement llvm codegen for 'airErrUnionPayload' for type {}", .{self.air.typeOf(ty_op.operand)}); + if (operand_is_ptr) { + return self.builder.buildStructGEP(operand, 1, ""); + } + + return self.builder.buildExtractValue(operand, 1, ""); } fn airErrUnionErr( @@ -1574,7 +1592,13 @@ pub const FuncGen = struct { if (!operand_is_ptr) return operand; return self.builder.buildLoad(operand, ""); } - return self.todo("implement llvm codegen for 'airErrUnionErr'", .{}); + + if (operand_is_ptr) { + const err_field_ptr = self.builder.buildStructGEP(operand, 0, ""); + return self.builder.buildLoad(err_field_ptr, ""); + } + + return self.builder.buildExtractValue(operand, 0, ""); } fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index e21645d1ee..4814ba0b55 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -1167,12 +1167,18 @@ pub const Context = struct { try leb.writeULEB128(writer, error_index); }, .ErrorUnion => { - const data = val.castTag(.error_union).?.data; const error_type = ty.errorUnionSet(); const payload_type = ty.errorUnionPayload(); - if (val.getError()) |_| { + if (val.castTag(.eu_payload)) |pl| { + const payload_val = pl.data; + // no error, so write a '0' const + try writer.writeByte(wasm.opcode(.i32_const)); + try leb.writeULEB128(writer, @as(u32, 0)); + // after the error code, we emit the payload + try self.emitConstant(payload_val, payload_type); + } else { // write the error val - try self.emitConstant(data, error_type); + try self.emitConstant(val, error_type); // no payload, so write a '0' const const opcode: wasm.Opcode = buildOpcode(.{ @@ -1181,12 +1187,6 @@ pub const Context = struct { }); try writer.writeByte(wasm.opcode(opcode)); try leb.writeULEB128(writer, @as(u32, 0)); - } else { - // no error, so write a '0' const - try writer.writeByte(wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, @as(u32, 0)); - // after the error code, we emit the payload - try self.emitConstant(data, payload_type); } }, .Optional => { diff --git a/src/value.zig b/src/value.zig index bf80c9d831..562d7171e8 100644 --- a/src/value.zig +++ b/src/value.zig @@ -129,7 +129,13 @@ pub const Value = extern union { /// A specific enum tag, indicated by the field index (declaration order). enum_field_index, @"error", - error_union, + /// When the type is error union: + /// * If the tag is `.@"error"`, the error union is an error. + /// * If the tag is `.eu_payload`, the error union is a payload. + /// * A nested error such as `((anyerror!T1)!T2)` in which the the outer error union + /// is non-error, but the inner error union is an error, is represented as + /// a tag of `.eu_payload`, with a sub-tag of `.@"error"`. + eu_payload, /// A pointer to the payload of an error union, based on a pointer to an error union. eu_payload_ptr, /// An instance of a struct. @@ -228,7 +234,7 @@ pub const Value = extern union { => Payload.Decl, .repeated, - .error_union, + .eu_payload, .eu_payload_ptr, => Payload.SubValue, @@ -450,7 +456,7 @@ pub const Value = extern union { return Value{ .ptr_otherwise = &new_payload.base }; }, .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), - .repeated, .error_union, .eu_payload_ptr => { + .repeated, .eu_payload, .eu_payload_ptr => { const payload = self.cast(Payload.SubValue).?; const new_payload = try allocator.create(Payload.SubValue); new_payload.* = .{ @@ -642,7 +648,10 @@ pub const Value = extern union { .float_128 => return out_stream.print("{}", .{val.castTag(.float_128).?.data}), .@"error" => return out_stream.print("error.{s}", .{val.castTag(.@"error").?.data.name}), // TODO to print this it should be error{ Set, Items }!T(val), but we need the type for that - .error_union => return out_stream.print("error_union_val({})", .{val.castTag(.error_union).?.data}), + .eu_payload => { + try out_stream.writeAll("(eu_payload) "); + val = val.castTag(.eu_payload).?.data; + }, .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"), .inferred_alloc_comptime => return out_stream.writeAll("(inferred comptime allocation value)"), .eu_payload_ptr => { @@ -1241,7 +1250,7 @@ pub const Value = extern union { .eu_payload_ptr => blk: { const err_union_ptr = self.castTag(.eu_payload_ptr).?.data; const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null; - break :blk err_union_val.castTag(.error_union).?.data; + break :blk err_union_val.castTag(.eu_payload).?.data; }, .zero, @@ -1351,16 +1360,16 @@ pub const Value = extern union { } /// Valid for all types. Asserts the value is not undefined and not unreachable. + /// Prefer `errorUnionIsPayload` to find out whether something is an error or not + /// because it works without having to figure out the string. pub fn getError(self: Value) ?[]const u8 { return switch (self.tag()) { - .error_union => { - const data = self.castTag(.error_union).?.data; - return if (data.tag() == .@"error") - data.castTag(.@"error").?.data.name - else - null; - }, .@"error" => self.castTag(.@"error").?.data.name, + .int_u64 => @panic("TODO"), + .int_i64 => @panic("TODO"), + .int_big_positive => @panic("TODO"), + .int_big_negative => @panic("TODO"), + .one => @panic("TODO"), .undef => unreachable, .unreachable_value => unreachable, .inferred_alloc => unreachable, @@ -1369,6 +1378,16 @@ pub const Value = extern union { else => null, }; } + + /// Assumes the type is an error union. Returns true if and only if the value is + /// the error union payload, not an error. + pub fn errorUnionIsPayload(val: Value) bool { + return switch (val.tag()) { + .eu_payload => true, + else => false, + }; + } + /// Valid for all types. Asserts the value is not undefined. pub fn isFloat(self: Value) bool { return switch (self.tag()) { diff --git a/test/behavior.zig b/test/behavior.zig index 936268af9c..a800b38458 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -7,6 +7,7 @@ test { _ = @import("behavior/generics.zig"); _ = @import("behavior/eval.zig"); _ = @import("behavior/pointers.zig"); + _ = @import("behavior/if.zig"); if (!builtin.zig_is_stage2) { // Tests that only pass for stage1. @@ -100,7 +101,7 @@ test { _ = @import("behavior/generics_stage1.zig"); _ = @import("behavior/hasdecl.zig"); _ = @import("behavior/hasfield.zig"); - _ = @import("behavior/if.zig"); + _ = @import("behavior/if_stage1.zig"); _ = @import("behavior/import.zig"); _ = @import("behavior/incomplete_struct_param_tld.zig"); _ = @import("behavior/inttoptr.zig"); diff --git a/test/behavior/if.zig b/test/behavior/if.zig index e8c84f4570..191d4817df 100644 --- a/test/behavior/if.zig +++ b/test/behavior/if.zig @@ -65,45 +65,3 @@ test "labeled break inside comptime if inside runtime if" { } try expect(answer == 42); } - -test "const result loc, runtime if cond, else unreachable" { - const Num = enum { - One, - Two, - }; - - var t = true; - const x = if (t) Num.Two else unreachable; - try expect(x == .Two); -} - -test "if prongs cast to expected type instead of peer type resolution" { - const S = struct { - fn doTheTest(f: bool) !void { - var x: i32 = 0; - x = if (f) 1 else 2; - try expect(x == 2); - - var b = true; - const y: i32 = if (b) 1 else 2; - try expect(y == 1); - } - }; - try S.doTheTest(false); - comptime try S.doTheTest(false); -} - -test "while copies its payload" { - const S = struct { - fn doTheTest() !void { - var tmp: ?i32 = 10; - if (tmp) |value| { - // Modify the original variable - tmp = null; - try expectEqual(@as(i32, 10), value); - } else unreachable; - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} diff --git a/test/behavior/if_stage1.zig b/test/behavior/if_stage1.zig new file mode 100644 index 0000000000..36500fbaee --- /dev/null +++ b/test/behavior/if_stage1.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; + +test "const result loc, runtime if cond, else unreachable" { + const Num = enum { + One, + Two, + }; + + var t = true; + const x = if (t) Num.Two else unreachable; + try expect(x == .Two); +} + +test "if prongs cast to expected type instead of peer type resolution" { + const S = struct { + fn doTheTest(f: bool) !void { + var x: i32 = 0; + x = if (f) 1 else 2; + try expect(x == 2); + + var b = true; + const y: i32 = if (b) 1 else 2; + try expect(y == 1); + } + }; + try S.doTheTest(false); + comptime try S.doTheTest(false); +} + +test "while copies its payload" { + const S = struct { + fn doTheTest() !void { + var tmp: ?i32 = 10; + if (tmp) |value| { + // Modify the original variable + tmp = null; + try expectEqual(@as(i32, 10), value); + } else unreachable; + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} -- cgit v1.2.3