From 67a44211f7a442d33096cc0dfff059eee9315bc6 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 14:04:13 +0300 Subject: Sema: improve handling of always_tail call modifier Closes #4301 Closes #5692 Closes #6281 Closes #10786 Closes #11149 Closes #11776 --- src/Sema.zig | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) (limited to 'src/Sema.zig') diff --git a/src/Sema.zig b/src/Sema.zig index a4e2106afb..823ae9baf2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6152,9 +6152,23 @@ fn analyzeCall( if (ensure_result_used) { try sema.ensureResultUsed(block, result, call_src); } + if (call_tag == .call_always_tail) { + return sema.handleTailCall(block, call_src, func_ty, result); + } return result; } +fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref { + const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl); + if (!func_ty.eql(func_decl.ty, sema.mod)) { + return sema.fail(block, call_src, "unable to perform tail call: type of function being called '{}' does not match type of calling function '{}'", .{ + func_ty.fmt(sema.mod), func_decl.ty.fmt(sema.mod), + }); + } + _ = try block.addUnOp(.ret, result); + return Air.Inst.Ref.unreachable_value; +} + fn analyzeInlineCallArg( sema: *Sema, arg_block: *Block, @@ -6670,7 +6684,8 @@ fn instantiateGenericCall( try sema.requireFunctionBlock(block, call_src); const comptime_args = callee.comptime_args.?; - const new_fn_info = mod.declPtr(callee.owner_decl).ty.fnInfo(); + const func_ty = mod.declPtr(callee.owner_decl).ty; + const new_fn_info = func_ty.fnInfo(); const runtime_args_len = @intCast(u32, new_fn_info.param_types.len); const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); { @@ -6717,7 +6732,7 @@ fn instantiateGenericCall( try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len + runtime_args_len); - const func_inst = try block.addInst(.{ + const result = try block.addInst(.{ .tag = call_tag, .data = .{ .pl_op = .{ .operand = callee_inst, @@ -6729,9 +6744,12 @@ fn instantiateGenericCall( sema.appendRefsAssumeCapacity(runtime_args); if (ensure_result_used) { - try sema.ensureResultUsed(block, func_inst, call_src); + try sema.ensureResultUsed(block, result, call_src); } - return func_inst; + if (call_tag == .call_always_tail) { + return sema.handleTailCall(block, call_src, func_ty, result); + } + return result; } fn emitDbgInline( @@ -19262,7 +19280,7 @@ fn resolveCallOptions( return wanted_modifier; }, // These can be upgraded to comptime. nosuspend bit can be safely ignored. - .always_tail, .always_inline, .compile_time => { + .always_inline, .compile_time => { _ = (try sema.resolveDefinedValue(block, func_src, func)) orelse { return sema.fail(block, func_src, "modifier '{s}' requires a comptime-known function", .{@tagName(wanted_modifier)}); }; @@ -19272,6 +19290,12 @@ fn resolveCallOptions( } return wanted_modifier; }, + .always_tail => { + if (is_comptime) { + return .compile_time; + } + return wanted_modifier; + }, .async_kw => { if (is_nosuspend) { return sema.fail(block, modifier_src, "modifier 'async_kw' cannot be used inside nosuspend block", .{}); -- cgit v1.2.3 From 01d19a8d3cdd52ad50f45bfb8666b56ccf8d3a22 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 14:40:48 +0300 Subject: Sema: do not emit generic poison for non generic parameters Closes #12679 --- src/Sema.zig | 18 ++++++++++++++++-- .../non-comptime-parameter-used-as-array-size.zig | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 test/cases/compile_errors/non-comptime-parameter-used-as-array-size.zig (limited to 'src/Sema.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 823ae9baf2..bf147a7cb4 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8195,8 +8195,22 @@ fn zirParam( .is_comptime = comptime_syntax, .name = param_name, }); - const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); - try sema.inst_map.putNoClobber(sema.gpa, inst, result); + + if (is_comptime) { + // If this is a comptime parameter we can add a constant generic_poison + // since this is also a generic parameter. + const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); + try sema.inst_map.putNoClobber(sema.gpa, inst, result); + } else { + // Otherwise we need a dummy runtime instruction. + const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len); + try sema.air_instructions.append(sema.gpa, .{ + .tag = .alloc, + .data = .{ .ty = param_ty }, + }); + const result = Air.indexToRef(result_index); + try sema.inst_map.putNoClobber(sema.gpa, inst, result); + } } fn zirParamAnytype( diff --git a/test/cases/compile_errors/non-comptime-parameter-used-as-array-size.zig b/test/cases/compile_errors/non-comptime-parameter-used-as-array-size.zig new file mode 100644 index 0000000000..b5495480ed --- /dev/null +++ b/test/cases/compile_errors/non-comptime-parameter-used-as-array-size.zig @@ -0,0 +1,16 @@ +export fn entry() void { + const llamas1 = makeLlamas(5); + const llamas2 = makeLlamas(5); + _ = llamas1; + _ = llamas2; +} + +fn makeLlamas(count: usize) [count]u8 { + _ = count; +} + +// error +// target=native +// +// :8:30: error: unable to resolve comptime value +// :8:30: note: array length must be comptime known -- cgit v1.2.3 From d3b4b2edf140c002da4c9c1396c26e0f66835eb0 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 15:21:50 +0300 Subject: Sema: shift of comptime int with runtime value Closes #12290 --- src/AstGen.zig | 33 ++++--- src/Autodoc.zig | 2 +- src/Sema.zig | 102 +++++++++++++++------ src/Zir.zig | 15 +-- src/print_zir.zig | 11 +-- .../compile_errors/runtime_to_comptime_num.zig | 31 +++++++ ...shifting_without_int_type_or_comptime_known.zig | 23 +++++ ...shifting_without_int_type_or_comptime_known.zig | 9 -- 8 files changed, 167 insertions(+), 59 deletions(-) create mode 100644 test/cases/compile_errors/runtime_to_comptime_num.zig create mode 100644 test/cases/compile_errors/shifting_without_int_type_or_comptime_known.zig delete mode 100644 test/cases/compile_errors/stage1/obj/shifting_without_int_type_or_comptime_known.zig (limited to 'src/Sema.zig') diff --git a/src/AstGen.zig b/src/AstGen.zig index 943d0aad08..79e5ad963e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -226,6 +226,8 @@ pub const ResultLoc = union(enum) { 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 for shift operands. + ty_shift_operand: Zir.Inst.Ref, /// Same as `ty` but it is guaranteed that Sema will additionally perform the coercion, /// so no `as` instruction needs to be emitted. coerced_ty: Zir.Inst.Ref, @@ -259,7 +261,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. - .none, .ty, .coerced_ty, .ref => return .{ + .none, .ty, .ty_shift_operand, .coerced_ty, .ref => return .{ .tag = .break_operand, .elide_store_to_block_ptr_instructions = false, }, @@ -302,6 +304,14 @@ pub const ResultLoc = union(enum) { else => rl, }; } + + fn zirTag(rl: ResultLoc) Zir.Inst.Tag { + return switch (rl) { + .ty => .as_node, + .ty_shift_operand => .as_shift_operand, + else => unreachable, + }; + } }; pub const align_rl: ResultLoc = .{ .ty = .u29_type }; @@ -1385,7 +1395,7 @@ fn arrayInitExpr( const tag: Zir.Inst.Tag = if (types.array != .none) .array_init else .array_init_anon; return arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag); }, - .ty, .coerced_ty => { + .ty, .ty_shift_operand, .coerced_ty => { const tag: Zir.Inst.Tag = if (types.array != .none) .array_init else .array_init_anon; const result = try arrayInitExprInner(gz, scope, node, array_init.ast.elements, types.array, types.elem, tag); return rvalue(gz, rl, result, node); @@ -1631,7 +1641,7 @@ fn structInitExpr( return structInitExprRlNone(gz, scope, node, struct_init, .none, .struct_init_anon); } }, - .ty, .coerced_ty => |ty_inst| { + .ty, .ty_shift_operand, .coerced_ty => |ty_inst| { if (struct_init.ast.type_expr == 0) { const result = try structInitExprRlNone(gz, scope, node, struct_init, ty_inst, .struct_init_anon); return rvalue(gz, rl, result, node); @@ -2327,6 +2337,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .anyframe_type, .as, .as_node, + .as_shift_operand, .bit_and, .bitcast, .bit_or, @@ -2497,7 +2508,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .field_parent_ptr, .maximum, .minimum, - .builtin_async_call, .c_import, .@"resume", .@"await", @@ -7278,7 +7288,7 @@ fn as( ) InnerError!Zir.Inst.Ref { const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { - .none, .discard, .ref, .ty, .coerced_ty => { + .none, .discard, .ref, .ty, .ty_shift_operand, .coerced_ty => { const result = try reachableExpr(gz, scope, .{ .ty = dest_type }, rhs, node); return rvalue(gz, rl, result, node); }, @@ -7959,7 +7969,8 @@ fn builtinCall( return rvalue(gz, rl, result, node); }, .async_call => { - const result = try gz.addPlNode(.builtin_async_call, node, Zir.Inst.AsyncCall{ + const result = try gz.addExtendedPayload(.builtin_async_call, Zir.Inst.AsyncCall{ + .node = gz.nodeIndexToRelative(node), .frame_buffer = try expr(gz, scope, .none, params[0]), .result_ptr = try expr(gz, scope, .none, params[1]), .fn_ptr = try expr(gz, scope, .none, params[2]), @@ -8178,7 +8189,7 @@ fn shiftOp( ) InnerError!Zir.Inst.Ref { const lhs = try expr(gz, scope, .none, lhs_node); const log2_int_type = try gz.addUnNode(.typeof_log2_int_type, lhs, lhs_node); - const rhs = try expr(gz, scope, .{ .ty = log2_int_type }, rhs_node); + const rhs = try expr(gz, scope, .{ .ty_shift_operand = log2_int_type }, rhs_node); const result = try gz.addPlNode(tag, node, Zir.Inst.Bin{ .lhs = lhs, .rhs = rhs, @@ -9409,7 +9420,7 @@ fn rvalue( } return indexToRef(gop.value_ptr.*); }, - .ty => |ty_inst| { + .ty, .ty_shift_operand => |ty_inst| { // Quickly eliminate some common, unnecessary type coercion. const as_ty = @as(u64, @enumToInt(Zir.Inst.Ref.type_type)) << 32; const as_comptime_int = @as(u64, @enumToInt(Zir.Inst.Ref.comptime_int_type)) << 32; @@ -9470,7 +9481,7 @@ fn rvalue( => return result, // type of result is already correct // Need an explicit type coercion instruction. - else => return gz.addPlNode(.as_node, src_node, Zir.Inst.As{ + else => return gz.addPlNode(rl.zirTag(), src_node, Zir.Inst.As{ .dest_type = ty_inst, .operand = result, }), @@ -10350,7 +10361,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, .coerced_ty => |ty_inst| { + .ty, .ty_shift_operand, .coerced_ty => |ty_inst| { gz.rl_ty_inst = ty_inst; gz.break_result_loc = parent_rl; }, @@ -11506,7 +11517,7 @@ const GenZir = struct { fn addRet(gz: *GenZir, rl: ResultLoc, operand: Zir.Inst.Ref, node: Ast.Node.Index) !void { switch (rl) { .ptr => |ret_ptr| _ = try gz.addUnNode(.ret_load, ret_ptr, node), - .ty => _ = try gz.addUnNode(.ret_node, operand, node), + .ty, .ty_shift_operand => _ = try gz.addUnNode(.ret_node, operand, node), else => unreachable, } } diff --git a/src/Autodoc.zig b/src/Autodoc.zig index db681157ae..0e056c093f 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -1888,7 +1888,7 @@ fn walkInstruction( .expr = .{ .typeInfo = operand_index }, }; }, - .as_node => { + .as_node, .as_shift_operand => { const pl_node = data[inst_index].pl_node; const extra = file.zir.extraData(Zir.Inst.As, pl_node.payload_index); const dest_type_walk = try self.walkRef( diff --git a/src/Sema.zig b/src/Sema.zig index bf147a7cb4..1b35f52437 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -712,6 +712,7 @@ fn analyzeBodyInner( .vector_type => try sema.zirVectorType(block, inst), .as => try sema.zirAs(block, inst), .as_node => try sema.zirAsNode(block, inst), + .as_shift_operand => try sema.zirAsShiftOperand(block, inst), .bit_and => try sema.zirBitwise(block, inst, .bit_and), .bit_not => try sema.zirBitNot(block, inst), .bit_or => try sema.zirBitwise(block, inst, .bit_or), @@ -848,7 +849,6 @@ fn analyzeBodyInner( .mul_add => try sema.zirMulAdd(block, inst), .builtin_call => try sema.zirBuiltinCall(block, inst), .field_parent_ptr => try sema.zirFieldParentPtr(block, inst), - .builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst), .@"resume" => try sema.zirResume(block, inst), .@"await" => try sema.zirAwait(block, inst), .array_base_ptr => try sema.zirArrayBasePtr(block, inst), @@ -956,6 +956,7 @@ fn analyzeBodyInner( .error_to_int => try sema.zirErrorToInt( block, extended), .int_to_error => try sema.zirIntToError( block, extended), .reify => try sema.zirReify( block, extended, inst), + .builtin_async_call => try sema.zirBuiltinAsyncCall( block, extended), // zig fmt: on .fence => { try sema.zirFence(block, extended); @@ -8257,7 +8258,7 @@ fn zirAs(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst defer tracy.end(); const bin_inst = sema.code.instructions.items(.data)[inst].bin; - return sema.analyzeAs(block, sema.src, bin_inst.lhs, bin_inst.rhs); + return sema.analyzeAs(block, sema.src, bin_inst.lhs, bin_inst.rhs, false); } fn zirAsNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8267,7 +8268,17 @@ fn zirAsNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const extra = sema.code.extraData(Zir.Inst.As, inst_data.payload_index).data; - return sema.analyzeAs(block, src, extra.dest_type, extra.operand); + return sema.analyzeAs(block, src, extra.dest_type, extra.operand, false); +} + +fn zirAsShiftOperand(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const tracy = trace(@src()); + defer tracy.end(); + + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.As, inst_data.payload_index).data; + return sema.analyzeAs(block, src, extra.dest_type, extra.operand, true); } fn analyzeAs( @@ -8276,6 +8287,7 @@ fn analyzeAs( src: LazySrcLoc, zir_dest_type: Zir.Inst.Ref, zir_operand: Zir.Inst.Ref, + no_cast_to_comptime_int: bool, ) CompileError!Air.Inst.Ref { const is_ret = if (Zir.refToIndex(zir_dest_type)) |ptr_index| sema.code.instructions.items(.tag)[ptr_index] == .ret_type @@ -8287,7 +8299,7 @@ fn analyzeAs( if (dest_ty.zigTypeTag() == .NoReturn) { return sema.fail(block, src, "cannot cast to noreturn", .{}); } - return sema.coerceExtra(block, dest_ty, operand, src, true, is_ret) catch |err| switch (err) { + return sema.coerceExtra(block, dest_ty, operand, src, .{ .is_ret = is_ret, .no_cast_to_comptime_int = no_cast_to_comptime_int }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -10491,7 +10503,12 @@ fn zirShl( const runtime_src = if (maybe_lhs_val) |lhs_val| rs: { if (lhs_val.isUndef()) return sema.addConstUndef(lhs_ty); - const rhs_val = maybe_rhs_val orelse break :rs rhs_src; + const rhs_val = maybe_rhs_val orelse { + if (scalar_ty.zigTypeTag() == .ComptimeInt) { + return sema.fail(block, src, "LHS of shift must be a fixed-width integer type, or RHS must be a comptime known", .{}); + } + break :rs rhs_src; + }; const val = switch (air_tag) { .shl_exact => val: { @@ -10615,7 +10632,10 @@ fn zirShr( const target = sema.mod.getTarget(); const scalar_ty = lhs_ty.scalarType(); - const runtime_src = if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| rs: { + const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); + + const runtime_src = if (maybe_rhs_val) |rhs_val| rs: { if (rhs_val.isUndef()) { return sema.addConstUndef(lhs_ty); } @@ -10647,7 +10667,7 @@ fn zirShr( }); } } - if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { + if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { return sema.addConstUndef(lhs_ty); } @@ -10665,6 +10685,10 @@ fn zirShr( } } else rhs_src; + if (maybe_rhs_val == null and scalar_ty.zigTypeTag() == .ComptimeInt) { + return sema.fail(block, src, "LHS of shift must be a fixed-width integer type, or RHS must be a comptime known", .{}); + } + try sema.requireRuntimeBlock(block, src, runtime_src); const result = try block.addBinOp(air_tag, lhs, rhs); if (block.wantSafety()) { @@ -15385,7 +15409,7 @@ fn analyzeRet( if (sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) { try sema.addToInferredErrorSet(uncasted_operand); } - const operand = sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, src, true, true) catch |err| switch (err) { + const operand = sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, src, .{ .is_ret = true }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -19652,9 +19676,9 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void }); } -fn zirBuiltinAsyncCall(sema: *Sema, block: *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(); +fn zirBuiltinAsyncCall(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { + const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; + const src = LazySrcLoc.nodeOffset(extra.node); return sema.failWithUseOfAsync(block, src); } @@ -22542,7 +22566,7 @@ fn coerce( inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, true, false) catch |err| switch (err) { + return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, .{}) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -22554,14 +22578,22 @@ const CoersionError = CompileError || error{ NotCoercible, }; +const CoerceOpts = struct { + /// Should coerceExtra emit error messages. + report_err: bool = true, + /// Ignored if `report_err == false`. + is_ret: bool = false, + /// Should coercion to comptime_int ermit an error message. + no_cast_to_comptime_int: bool = false, +}; + fn coerceExtra( sema: *Sema, block: *Block, dest_ty_unresolved: Type, inst: Air.Inst.Ref, inst_src: LazySrcLoc, - report_err: bool, - is_ret: bool, + opts: CoerceOpts, ) CoersionError!Air.Inst.Ref { switch (dest_ty_unresolved.tag()) { .var_args_param => return sema.coerceVarArgParam(block, inst, inst_src), @@ -22613,7 +22645,7 @@ fn coerceExtra( // T to ?T const child_type = try dest_ty.optionalChildAlloc(sema.arena); - const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, false, is_ret) catch |err| switch (err) { + const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => { if (in_memory_result == .no_match) { // Try to give more useful notes @@ -22729,7 +22761,7 @@ fn coerceExtra( return sema.addConstant(dest_ty, Value.@"null"); }, .ComptimeInt => { - const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, false, is_ret) catch |err| switch (err) { + const addr = sema.coerceExtra(block, Type.usize, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => break :pointer, else => |e| return e, }; @@ -22740,7 +22772,7 @@ fn coerceExtra( .signed => Type.isize, .unsigned => Type.usize, }; - const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, false, is_ret) catch |err| switch (err) { + const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) { error.NotCoercible => { // Try to give more useful notes in_memory_result = try sema.coerceInMemoryAllowed(block, ptr_size_ty, inst_ty, false, target, dest_ty_src, inst_src); @@ -22866,7 +22898,13 @@ fn coerceExtra( }, .Int, .ComptimeInt => switch (inst_ty.zigTypeTag()) { .Float, .ComptimeFloat => float: { - const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse break :float; + const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse { + if (dest_ty.zigTypeTag() == .ComptimeInt) { + if (!opts.report_err) return error.NotCoercible; + return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_int' must be comptime known"); + } + break :float; + }; if (val.floatHasFraction()) { return sema.fail( @@ -22883,11 +22921,16 @@ fn coerceExtra( if (try sema.resolveDefinedValue(block, inst_src, inst)) |val| { // comptime known integer to other number if (!(try sema.intFitsInType(block, inst_src, val, dest_ty, null))) { - if (!report_err) return error.NotCoercible; + if (!opts.report_err) return error.NotCoercible; return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(sema.mod), val.fmtValue(inst_ty, sema.mod) }); } return try sema.addConstant(dest_ty, val); } + if (dest_ty.zigTypeTag() == .ComptimeInt) { + if (!opts.report_err) return error.NotCoercible; + if (opts.no_cast_to_comptime_int) return inst; + return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_int' must be comptime known"); + } // integer widening const dst_info = dest_ty.intInfo(target); @@ -22924,6 +22967,7 @@ fn coerceExtra( } return try sema.addConstant(dest_ty, result_val); } else if (dest_ty.zigTypeTag() == .ComptimeFloat) { + if (!opts.report_err) return error.NotCoercible; return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_float' must be comptime known"); } @@ -22936,7 +22980,13 @@ fn coerceExtra( } }, .Int, .ComptimeInt => int: { - const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse break :int; + const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse { + if (dest_ty.zigTypeTag() == .ComptimeFloat) { + if (!opts.report_err) return error.NotCoercible; + return sema.failWithNeededComptime(block, inst_src, "value being casted to 'comptime_float' must be comptime known"); + } + break :int; + }; const result_val = try val.intToFloat(sema.arena, inst_ty, dest_ty, target); // TODO implement this compile error //const int_again_val = try result_val.floatToInt(sema.arena, inst_ty); @@ -23088,9 +23138,9 @@ fn coerceExtra( return sema.addConstUndef(dest_ty); } - if (!report_err) return error.NotCoercible; + if (!opts.report_err) return error.NotCoercible; - if (is_ret and dest_ty.zigTypeTag() == .NoReturn) { + if (opts.is_ret and dest_ty.zigTypeTag() == .NoReturn) { const msg = msg: { const msg = try sema.errMsg(block, inst_src, "function declared 'noreturn' returns", .{}); errdefer msg.destroy(sema.gpa); @@ -23127,7 +23177,7 @@ fn coerceExtra( try in_memory_result.report(sema, block, inst_src, msg); // Add notes about function return type - if (is_ret and sema.mod.test_functions.get(sema.func.?.owner_decl) == null) { + if (opts.is_ret and sema.mod.test_functions.get(sema.func.?.owner_decl) == null) { const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = 0 }; const src_decl = sema.mod.declPtr(sema.func.?.owner_decl); if (inst_ty.isError() and !dest_ty.isError()) { @@ -24088,7 +24138,7 @@ fn storePtr2( // https://github.com/ziglang/zig/issues/11154 if (sema.obtainBitCastedVectorPtr(ptr)) |vector_ptr| { const vector_ty = sema.typeOf(vector_ptr).childType(); - const vector = sema.coerceExtra(block, vector_ty, uncasted_operand, operand_src, true, is_ret) catch |err| switch (err) { + const vector = sema.coerceExtra(block, vector_ty, uncasted_operand, operand_src, .{ .is_ret = is_ret }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -24096,7 +24146,7 @@ fn storePtr2( return; } - const operand = sema.coerceExtra(block, elem_ty, uncasted_operand, operand_src, true, is_ret) catch |err| switch (err) { + const operand = sema.coerceExtra(block, elem_ty, uncasted_operand, operand_src, .{ .is_ret = is_ret }) catch |err| switch (err) { error.NotCoercible => unreachable, else => |e| return e, }; @@ -26831,7 +26881,7 @@ fn wrapErrorUnionPayload( inst_src: LazySrcLoc, ) !Air.Inst.Ref { const dest_payload_ty = dest_ty.errorUnionPayload(); - const coerced = try sema.coerceExtra(block, dest_payload_ty, inst, inst_src, false, false); + const coerced = try sema.coerceExtra(block, dest_payload_ty, inst, inst_src, .{ .report_err = false }); if (try sema.resolveMaybeUndefVal(block, inst_src, coerced)) |val| { return sema.addConstant(dest_ty, try Value.Tag.eu_payload.create(sema.arena, val)); } diff --git a/src/Zir.zig b/src/Zir.zig index ec9ddfcffb..3ce086e10b 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -242,6 +242,8 @@ pub const Inst = struct { /// Type coercion to the function's return type. /// Uses the `pl_node` field. Payload is `As`. AST node could be many things. as_node, + /// Same as `as_node` but ignores runtime to comptime int error. + as_shift_operand, /// Bitwise AND. `&` bit_and, /// Reinterpret the memory representation of a value as a different type. @@ -942,9 +944,6 @@ pub const Inst = struct { /// Implements the `@maximum` builtin. /// Uses the `pl_node` union field with payload `Bin` maximum, - /// Implements the `@asyncCall` builtin. - /// Uses the `pl_node` union field with payload `AsyncCall`. - builtin_async_call, /// Implements the `@cImport` builtin. /// Uses the `pl_node` union field with payload `Block`. c_import, @@ -1029,6 +1028,7 @@ pub const Inst = struct { .anyframe_type, .as, .as_node, + .as_shift_operand, .bit_and, .bitcast, .bit_or, @@ -1231,7 +1231,6 @@ pub const Inst = struct { .memcpy, .memset, .minimum, - .builtin_async_call, .c_import, .@"resume", .@"await", @@ -1339,6 +1338,7 @@ pub const Inst = struct { .anyframe_type, .as, .as_node, + .as_shift_operand, .bit_and, .bitcast, .bit_or, @@ -1513,7 +1513,6 @@ pub const Inst = struct { .field_parent_ptr, .maximum, .minimum, - .builtin_async_call, .c_import, .@"resume", .@"await", @@ -1577,6 +1576,7 @@ pub const Inst = struct { .anyframe_type = .un_node, .as = .bin, .as_node = .pl_node, + .as_shift_operand = .pl_node, .bit_and = .pl_node, .bitcast = .pl_node, .bit_not = .un_node, @@ -1801,7 +1801,6 @@ pub const Inst = struct { .memcpy = .pl_node, .memset = .pl_node, .minimum = .pl_node, - .builtin_async_call = .pl_node, .c_import = .pl_node, .alloc = .un_node, @@ -1972,6 +1971,9 @@ pub const Inst = struct { /// `operand` is payload index to `UnNode`. /// `small` contains `NameStrategy reify, + /// Implements the `@asyncCall` builtin. + /// `operand` is payload index to `AsyncCall`. + builtin_async_call, pub const InstData = struct { opcode: Extended, @@ -3454,6 +3456,7 @@ pub const Inst = struct { }; pub const AsyncCall = struct { + node: i32, frame_buffer: Ref, result_ptr: Ref, fn_ptr: Ref, diff --git a/src/print_zir.zig b/src/print_zir.zig index f315d7f014..976d012138 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -283,7 +283,6 @@ const Writer = struct { .mul_add => try self.writeMulAdd(stream, inst), .field_parent_ptr => try self.writeFieldParentPtr(stream, inst), .builtin_call => try self.writeBuiltinCall(stream, inst), - .builtin_async_call => try self.writeBuiltinAsyncCall(stream, inst), .struct_init_anon, .struct_init_anon_ref, @@ -397,7 +396,7 @@ const Writer = struct { .field_val_named, => try self.writePlNodeFieldNamed(stream, inst), - .as_node => try self.writeAs(stream, inst), + .as_node, .as_shift_operand => try self.writeAs(stream, inst), .repeat, .repeat_inline, @@ -531,6 +530,7 @@ const Writer = struct { try stream.writeAll(") "); try self.writeSrc(stream, src); }, + .builtin_async_call => try self.writeBuiltinAsyncCall(stream, extended), } } @@ -814,9 +814,8 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writeBuiltinAsyncCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Zir.Inst.AsyncCall, inst_data.payload_index).data; + fn writeBuiltinAsyncCall(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.AsyncCall, extended.operand).data; try self.writeInstRef(stream, extra.frame_buffer); try stream.writeAll(", "); try self.writeInstRef(stream, extra.result_ptr); @@ -825,7 +824,7 @@ const Writer = struct { try stream.writeAll(", "); try self.writeInstRef(stream, extra.args); try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); + try self.writeSrc(stream, LazySrcLoc.nodeOffset(extra.node)); } fn writeParam(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { diff --git a/test/cases/compile_errors/runtime_to_comptime_num.zig b/test/cases/compile_errors/runtime_to_comptime_num.zig new file mode 100644 index 0000000000..2275e35c43 --- /dev/null +++ b/test/cases/compile_errors/runtime_to_comptime_num.zig @@ -0,0 +1,31 @@ +pub export fn entry() void { + var a: u32 = 0; + _ = @as(comptime_int, a); +} +pub export fn entry2() void{ + var a: u32 = 0; + _ = @as(comptime_float, a); +} +pub export fn entry3() void{ + comptime var aa: comptime_float = 0.0; + var a: f32 = 4; + aa = a; +} +pub export fn entry4() void{ + comptime var aa: comptime_int = 0.0; + var a: f32 = 4; + aa = a; +} + +// error +// backend=stage2 +// target=native +// +// :3:27: error: unable to resolve comptime value +// :3:27: note: value being casted to 'comptime_int' must be comptime known +// :7:29: error: unable to resolve comptime value +// :7:29: note: value being casted to 'comptime_float' must be comptime known +// :12:10: error: unable to resolve comptime value +// :12:10: note: value being casted to 'comptime_float' must be comptime known +// :17:10: error: unable to resolve comptime value +// :17:10: note: value being casted to 'comptime_int' must be comptime known diff --git a/test/cases/compile_errors/shifting_without_int_type_or_comptime_known.zig b/test/cases/compile_errors/shifting_without_int_type_or_comptime_known.zig new file mode 100644 index 0000000000..7e68baed62 --- /dev/null +++ b/test/cases/compile_errors/shifting_without_int_type_or_comptime_known.zig @@ -0,0 +1,23 @@ +export fn entry(x: u8) u8 { + return 0x11 << x; +} +export fn entry1(x: u8) u8 { + return 0x11 >> x; +} +export fn entry2() void { + var x: u5 = 1; + _ = @shlExact(12345, x); +} +export fn entry3() void { + var x: u5 = 1; + _ = @shrExact(12345, x); +} + +// error +// backend=stage2 +// target=native +// +// :2:17: error: LHS of shift must be a fixed-width integer type, or RHS must be a comptime known +// :5:17: error: LHS of shift must be a fixed-width integer type, or RHS must be a comptime known +// :9:9: error: LHS of shift must be a fixed-width integer type, or RHS must be a comptime known +// :13:9: error: LHS of shift must be a fixed-width integer type, or RHS must be a comptime known diff --git a/test/cases/compile_errors/stage1/obj/shifting_without_int_type_or_comptime_known.zig b/test/cases/compile_errors/stage1/obj/shifting_without_int_type_or_comptime_known.zig deleted file mode 100644 index 4d875575d0..0000000000 --- a/test/cases/compile_errors/stage1/obj/shifting_without_int_type_or_comptime_known.zig +++ /dev/null @@ -1,9 +0,0 @@ -export fn entry(x: u8) u8 { - return 0x11 << x; -} - -// error -// backend=stage1 -// target=native -// -// tmp.zig:2:17: error: LHS of shift must be a fixed-width integer type, or RHS must be compile-time known -- cgit v1.2.3 From c558de6655e2e9b72c5733b2d477ff18520d1c6b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 16:01:28 +0300 Subject: stage2 llvm: use tag value instead of field index in airUnionInit Closes #12656 --- src/Sema.zig | 12 ++++++------ src/codegen/llvm.zig | 19 ++++++++++++++++--- test/behavior/union.zig | 28 ++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) (limited to 'src/Sema.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 1b35f52437..74f19e5b93 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -21933,6 +21933,7 @@ fn unionFieldPtr( .mutable = union_ptr_ty.ptrIsMutable(), .@"addrspace" = union_ptr_ty.ptrAddressSpace(), }); + const enum_field_index = @intCast(u32, union_obj.tag_ty.enumFieldIndex(field_name).?); if (initializing and field.ty.zigTypeTag() == .NoReturn) { const msg = msg: { @@ -21954,11 +21955,10 @@ fn unionFieldPtr( if (union_val.isUndef()) { return sema.failWithUseOfUndef(block, src); } - const enum_field_index = union_obj.tag_ty.enumFieldIndex(field_name).?; const tag_and_val = union_val.castTag(.@"union").?.data; var field_tag_buf: Value.Payload.U32 = .{ .base = .{ .tag = .enum_field_index }, - .data = @intCast(u32, enum_field_index), + .data = enum_field_index, }; const field_tag = Value.initPayload(&field_tag_buf.base); const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod); @@ -21990,7 +21990,7 @@ fn unionFieldPtr( if (!initializing and union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1) { - const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); const wanted_tag = try sema.addConstant(union_obj.tag_ty, wanted_tag_val); // TODO would it be better if get_union_tag supported pointers to unions? const union_val = try block.addTyOp(.load, union_ty, union_ptr); @@ -22020,15 +22020,15 @@ fn unionFieldVal( const union_obj = union_ty.cast(Type.Payload.Union).?.data; const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_name_src); const field = union_obj.fields.values()[field_index]; + const enum_field_index = @intCast(u32, union_obj.tag_ty.enumFieldIndex(field_name).?); if (try sema.resolveMaybeUndefVal(block, src, union_byval)) |union_val| { if (union_val.isUndef()) return sema.addConstUndef(field.ty); const tag_and_val = union_val.castTag(.@"union").?.data; - const enum_field_index = union_obj.tag_ty.enumFieldIndex(field_name).?; var field_tag_buf: Value.Payload.U32 = .{ .base = .{ .tag = .enum_field_index }, - .data = @intCast(u32, enum_field_index), + .data = enum_field_index, }; const field_tag = Value.initPayload(&field_tag_buf.base); const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod); @@ -22064,7 +22064,7 @@ fn unionFieldVal( if (union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null and union_obj.fields.count() > 1) { - const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); const wanted_tag = try sema.addConstant(union_obj.tag_ty, wanted_tag_val); const active_tag = try block.addTyOp(.get_union_tag, union_obj.tag_ty, union_byval); const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index a431c14d5a..93d2eaa9df 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -8527,12 +8527,26 @@ pub const FuncGen = struct { const union_llvm_ty = try self.dg.lowerType(union_ty); const target = self.dg.module.getTarget(); const layout = union_ty.unionGetLayout(target); + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + const tag_int = blk: { + const tag_ty = union_ty.unionTagTypeHypothetical(); + const union_field_name = union_obj.fields.keys()[extra.field_index]; + const enum_field_index = tag_ty.enumFieldIndex(union_field_name).?; + var tag_val_payload: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = @intCast(u32, enum_field_index), + }; + const tag_val = Value.initPayload(&tag_val_payload.base); + var int_payload: Value.Payload.U64 = undefined; + const tag_int_val = tag_val.enumToInt(tag_ty, &int_payload); + break :blk tag_int_val.toUnsignedInt(target); + }; if (layout.payload_size == 0) { if (layout.tag_size == 0) { return null; } assert(!isByRef(union_ty)); - return union_llvm_ty.constInt(extra.field_index, .False); + return union_llvm_ty.constInt(tag_int, .False); } assert(isByRef(union_ty)); // The llvm type of the alloca will the the named LLVM union type, which will not @@ -8541,7 +8555,6 @@ pub const FuncGen = struct { // then set the fields appropriately. const result_ptr = self.buildAlloca(union_llvm_ty); const llvm_payload = try self.resolveInst(extra.init); - const union_obj = union_ty.cast(Type.Payload.Union).?.data; assert(union_obj.haveFieldTypes()); const field = union_obj.fields.values()[extra.field_index]; const field_llvm_ty = try self.dg.lowerType(field.ty); @@ -8625,7 +8638,7 @@ pub const FuncGen = struct { }; const field_ptr = self.builder.buildInBoundsGEP(casted_ptr, &indices, indices.len, ""); const tag_llvm_ty = try self.dg.lowerType(union_obj.tag_ty); - const llvm_tag = tag_llvm_ty.constInt(extra.field_index, .False); + const llvm_tag = tag_llvm_ty.constInt(tag_int, .False); const store_inst = self.builder.buildStore(llvm_tag, field_ptr); store_inst.setAlignment(union_obj.tag_ty.abiAlignment(target)); } diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 5d6b084be5..79bc1861e4 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1324,3 +1324,31 @@ test "union and enum field order doesn't match" { x = .b; try expect(x == .b); } + +test "@unionInit uses tag value instead of field index" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + + const E = enum(u8) { + b = 255, + a = 3, + }; + const U = union(E) { + a: usize, + b: isize, + }; + var i: isize = -1; + var u = @unionInit(U, "b", i); + { + var a = u.b; + try expect(a == i); + } + { + var a = &u.b; + try expect(a.* == i); + } + try expect(@enumToInt(u) == 255); +} -- cgit v1.2.3 From 65d37239682b6418b6dd25f07187ae99088e67f0 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 30 Aug 2022 17:13:35 +0300 Subject: Sema: check that target supports tail calls --- lib/std/target.zig | 10 ++++++++++ src/Sema.zig | 4 ++++ test/behavior/call.zig | 4 ++++ test/cases/taill_call_noreturn.zig | 2 +- 4 files changed, 19 insertions(+), 1 deletion(-) (limited to 'src/Sema.zig') diff --git a/lib/std/target.zig b/lib/std/target.zig index 64f9f97809..7b4a468a61 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1440,6 +1440,16 @@ pub const Target = struct { return !self.cpu.arch.isWasm(); } + pub fn supportsTailCall(self: Target) bool { + switch (self.cpu.arch) { + .wasm32, .wasm64 => return wasm.featureSetHas(self.cpu.features, .tail_call), + // TODO these might not be true but LLVM doesn't seem to be able to handle them + .mips, .mipsel, .mips64, .mips64el => return false, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => return false, + else => return true, + } + } + pub const FloatAbi = enum { hard, soft, diff --git a/src/Sema.zig b/src/Sema.zig index 74f19e5b93..8cde9d7e12 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6160,6 +6160,10 @@ fn analyzeCall( } fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref { + const target = sema.mod.getTarget(); + if (!target.supportsTailCall()) { + return sema.fail(block, call_src, "unable to perform tail call: target does not support tail calls", .{}); + } const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl); if (!func_ty.eql(func_decl.ty, sema.mod)) { return sema.fail(block, call_src, "unable to perform tail call: type of function being called '{}' does not match type of calling function '{}'", .{ diff --git a/test/behavior/call.zig b/test/behavior/call.zig index 4c697ed542..4a4ff4fb78 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -270,6 +270,8 @@ test "forced tail call" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest; + const S = struct { fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 { if (n == 0) return a; @@ -296,6 +298,8 @@ test "inline call preserves tail call" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest; + const max = std.math.maxInt(u16); const S = struct { var a: u16 = 0; diff --git a/test/cases/taill_call_noreturn.zig b/test/cases/taill_call_noreturn.zig index 0c2497d6ce..fabb9e729b 100644 --- a/test/cases/taill_call_noreturn.zig +++ b/test/cases/taill_call_noreturn.zig @@ -15,4 +15,4 @@ pub fn main() void { // run // backend=llvm -// target=native +// target=x86_64-linux,x86_64-macos,aarch64-linux,aarch64-macos -- cgit v1.2.3 From 7377dce368090e3c49a15d8996cc812adadd3d43 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Aug 2022 12:43:10 -0700 Subject: avoid exposing supportsTailCall in the standard library This is problematic because in practice it depends on whether the compiler backend supports it too, as evidenced by the TODO comment about LLVM not supporting some architectures that in fact do support tail calls. Instead this logic is organized strategically in src/target.zig, part of the internal compiler source code, and the behavior tests in question duplicate some logic for deciding whether to proceed with the test. The proper place to expose this flag is in `@import("builtin")` - the generated source file - so that third party compilers can advertise whether they support tail calls. --- lib/std/target.zig | 10 ----- src/Compilation.zig | 36 ++++++++-------- src/Sema.zig | 7 +++- src/codegen/llvm.zig | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ src/link.zig | 7 ++-- src/mingw.zig | 8 ++-- src/target.zig | 108 ++++-------------------------------------------- test/behavior/call.zig | 14 ++++++- 8 files changed, 161 insertions(+), 139 deletions(-) (limited to 'src/Sema.zig') diff --git a/lib/std/target.zig b/lib/std/target.zig index 7b4a468a61..64f9f97809 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1440,16 +1440,6 @@ pub const Target = struct { return !self.cpu.arch.isWasm(); } - pub fn supportsTailCall(self: Target) bool { - switch (self.cpu.arch) { - .wasm32, .wasm64 => return wasm.featureSetHas(self.cpu.features, .tail_call), - // TODO these might not be true but LLVM doesn't seem to be able to handle them - .mips, .mipsel, .mips64, .mips64el => return false, - .powerpc, .powerpcle, .powerpc64, .powerpc64le => return false, - else => return true, - } - } - pub const FloatAbi = enum { hard, soft, diff --git a/src/Compilation.zig b/src/Compilation.zig index 8e4b322230..c1321e40cf 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4766,6 +4766,24 @@ pub fn dump_argv(argv: []const []const u8) void { std.debug.print("{s}\n", .{argv[argv.len - 1]}); } +pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend { + const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1; + if (use_stage1) return .stage1; + if (build_options.have_llvm and comp.bin_file.options.use_llvm) return .stage2_llvm; + const target = comp.bin_file.options.target; + if (target.ofmt == .c) return .stage2_c; + return switch (target.cpu.arch) { + .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm, + .arm, .armeb, .thumb, .thumbeb => .stage2_arm, + .x86_64 => .stage2_x86_64, + .i386 => .stage2_x86, + .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64, + .riscv64 => .stage2_riscv64, + .sparc64 => .stage2_sparc64, + else => .other, + }; +} + pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Allocator.Error![:0]u8 { const tracy_trace = trace(@src()); defer tracy_trace.end(); @@ -4775,23 +4793,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca const target = comp.getTarget(); const generic_arch_name = target.cpu.arch.genericName(); - const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1; - - const zig_backend: std.builtin.CompilerBackend = blk: { - if (use_stage1) break :blk .stage1; - if (build_options.have_llvm and comp.bin_file.options.use_llvm) break :blk .stage2_llvm; - if (target.ofmt == .c) break :blk .stage2_c; - break :blk switch (target.cpu.arch) { - .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm, - .arm, .armeb, .thumb, .thumbeb => .stage2_arm, - .x86_64 => .stage2_x86_64, - .i386 => .stage2_x86, - .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64, - .riscv64 => .stage2_riscv64, - .sparc64 => .stage2_sparc64, - else => .other, - }; - }; + const zig_backend = comp.getZigBackend(); @setEvalBranchQuota(4000); try buffer.writer().print( diff --git a/src/Sema.zig b/src/Sema.zig index 8cde9d7e12..0626fd30ee 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6161,8 +6161,11 @@ fn analyzeCall( fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref { const target = sema.mod.getTarget(); - if (!target.supportsTailCall()) { - return sema.fail(block, call_src, "unable to perform tail call: target does not support tail calls", .{}); + const backend = sema.mod.comp.getZigBackend(); + if (!target_util.supportsTailCall(target, backend)) { + return sema.fail(block, call_src, "unable to perform tail call: compiler backend '{s}' does not support tail calls on target architecture '{s}' with the selected CPU feature flags", .{ + @tagName(backend), @tagName(target.cpu.arch), + }); } const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl); if (!func_ty.eql(func_decl.ty, sema.mod)) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 93d2eaa9df..45426c5ee0 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -177,6 +177,116 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 { return llvm_triple.toOwnedSliceSentinel(0); } +pub fn targetOs(os_tag: std.Target.Os.Tag) llvm.OSType { + return switch (os_tag) { + .freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS, + .windows, .uefi => .Win32, + .ananas => .Ananas, + .cloudabi => .CloudABI, + .dragonfly => .DragonFly, + .freebsd => .FreeBSD, + .fuchsia => .Fuchsia, + .ios => .IOS, + .kfreebsd => .KFreeBSD, + .linux => .Linux, + .lv2 => .Lv2, + .macos => .MacOSX, + .netbsd => .NetBSD, + .openbsd => .OpenBSD, + .solaris => .Solaris, + .zos => .ZOS, + .haiku => .Haiku, + .minix => .Minix, + .rtems => .RTEMS, + .nacl => .NaCl, + .aix => .AIX, + .cuda => .CUDA, + .nvcl => .NVCL, + .amdhsa => .AMDHSA, + .ps4 => .PS4, + .elfiamcu => .ELFIAMCU, + .tvos => .TvOS, + .watchos => .WatchOS, + .mesa3d => .Mesa3D, + .contiki => .Contiki, + .amdpal => .AMDPAL, + .hermit => .HermitCore, + .hurd => .Hurd, + .wasi => .WASI, + .emscripten => .Emscripten, + }; +} + +pub fn targetArch(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { + return switch (arch_tag) { + .arm => .arm, + .armeb => .armeb, + .aarch64 => .aarch64, + .aarch64_be => .aarch64_be, + .aarch64_32 => .aarch64_32, + .arc => .arc, + .avr => .avr, + .bpfel => .bpfel, + .bpfeb => .bpfeb, + .csky => .csky, + .hexagon => .hexagon, + .m68k => .m68k, + .mips => .mips, + .mipsel => .mipsel, + .mips64 => .mips64, + .mips64el => .mips64el, + .msp430 => .msp430, + .powerpc => .ppc, + .powerpcle => .ppcle, + .powerpc64 => .ppc64, + .powerpc64le => .ppc64le, + .r600 => .r600, + .amdgcn => .amdgcn, + .riscv32 => .riscv32, + .riscv64 => .riscv64, + .sparc => .sparc, + .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9. + .sparcel => .sparcel, + .s390x => .systemz, + .tce => .tce, + .tcele => .tcele, + .thumb => .thumb, + .thumbeb => .thumbeb, + .i386 => .x86, + .x86_64 => .x86_64, + .xcore => .xcore, + .nvptx => .nvptx, + .nvptx64 => .nvptx64, + .le32 => .le32, + .le64 => .le64, + .amdil => .amdil, + .amdil64 => .amdil64, + .hsail => .hsail, + .hsail64 => .hsail64, + .spir => .spir, + .spir64 => .spir64, + .kalimba => .kalimba, + .shave => .shave, + .lanai => .lanai, + .wasm32 => .wasm32, + .wasm64 => .wasm64, + .renderscript32 => .renderscript32, + .renderscript64 => .renderscript64, + .ve => .ve, + .spu_2, .spirv32, .spirv64 => .UnknownArch, + }; +} + +pub fn supportsTailCall(target: std.Target) bool { + switch (target.cpu.arch) { + .wasm32, .wasm64 => return std.Target.wasm.featureSetHas(target.cpu.features, .tail_call), + // Although these ISAs support tail calls, LLVM does not support tail calls on them. + .mips, .mipsel, .mips64, .mips64el => return false, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => return false, + else => return true, + } +} + pub const Object = struct { gpa: Allocator, module: *Module, diff --git a/src/link.zig b/src/link.zig index 421188bd47..a8845a0d57 100644 --- a/src/link.zig +++ b/src/link.zig @@ -931,9 +931,10 @@ pub const File = struct { std.debug.print("\n", .{}); } - const llvm = @import("codegen/llvm/bindings.zig"); - const os_type = @import("target.zig").osToLLVM(base.options.target.os.tag); - const bad = llvm.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_type); + const llvm_bindings = @import("codegen/llvm/bindings.zig"); + const llvm = @import("codegen/llvm.zig"); + const os_tag = llvm.targetOs(base.options.target.os.tag); + const bad = llvm_bindings.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_tag); if (bad) return error.UnableToWriteArchive; if (!base.options.disable_lld_caching) { diff --git a/src/mingw.zig b/src/mingw.zig index b50cc4b009..23035fe72b 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -6,7 +6,6 @@ const assert = std.debug.assert; const log = std.log.scoped(.mingw); const builtin = @import("builtin"); -const target_util = @import("target.zig"); const Compilation = @import("Compilation.zig"); const build_options = @import("build_options"); const Cache = @import("Cache.zig"); @@ -404,11 +403,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { }); errdefer comp.gpa.free(lib_final_path); - const llvm = @import("codegen/llvm/bindings.zig"); - const arch_type = target_util.archToLLVM(target.cpu.arch); + const llvm_bindings = @import("codegen/llvm/bindings.zig"); + const llvm = @import("codegen/llvm.zig"); + const arch_tag = llvm.targetArch(target.cpu.arch); const def_final_path_z = try arena.dupeZ(u8, def_final_path); const lib_final_path_z = try arena.dupeZ(u8, lib_final_path); - if (llvm.WriteImportLibrary(def_final_path_z.ptr, arch_type, lib_final_path_z.ptr, true)) { + if (llvm_bindings.WriteImportLibrary(def_final_path_z.ptr, arch_tag, lib_final_path_z.ptr, true)) { // TODO surface a proper error here log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name }); return error.WritingImportLibFailed; diff --git a/src/target.zig b/src/target.zig index 405a7fe2bf..f8b44bac0e 100644 --- a/src/target.zig +++ b/src/target.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const llvm = @import("codegen/llvm/bindings.zig"); const Type = @import("type.zig").Type; pub const ArchOsAbi = struct { @@ -317,106 +316,6 @@ pub fn supportsReturnAddress(target: std.Target) bool { }; } -pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType { - return switch (os_tag) { - .freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS, - .windows, .uefi => .Win32, - .ananas => .Ananas, - .cloudabi => .CloudABI, - .dragonfly => .DragonFly, - .freebsd => .FreeBSD, - .fuchsia => .Fuchsia, - .ios => .IOS, - .kfreebsd => .KFreeBSD, - .linux => .Linux, - .lv2 => .Lv2, - .macos => .MacOSX, - .netbsd => .NetBSD, - .openbsd => .OpenBSD, - .solaris => .Solaris, - .zos => .ZOS, - .haiku => .Haiku, - .minix => .Minix, - .rtems => .RTEMS, - .nacl => .NaCl, - .aix => .AIX, - .cuda => .CUDA, - .nvcl => .NVCL, - .amdhsa => .AMDHSA, - .ps4 => .PS4, - .elfiamcu => .ELFIAMCU, - .tvos => .TvOS, - .watchos => .WatchOS, - .mesa3d => .Mesa3D, - .contiki => .Contiki, - .amdpal => .AMDPAL, - .hermit => .HermitCore, - .hurd => .Hurd, - .wasi => .WASI, - .emscripten => .Emscripten, - }; -} - -pub fn archToLLVM(arch_tag: std.Target.Cpu.Arch) llvm.ArchType { - return switch (arch_tag) { - .arm => .arm, - .armeb => .armeb, - .aarch64 => .aarch64, - .aarch64_be => .aarch64_be, - .aarch64_32 => .aarch64_32, - .arc => .arc, - .avr => .avr, - .bpfel => .bpfel, - .bpfeb => .bpfeb, - .csky => .csky, - .hexagon => .hexagon, - .m68k => .m68k, - .mips => .mips, - .mipsel => .mipsel, - .mips64 => .mips64, - .mips64el => .mips64el, - .msp430 => .msp430, - .powerpc => .ppc, - .powerpcle => .ppcle, - .powerpc64 => .ppc64, - .powerpc64le => .ppc64le, - .r600 => .r600, - .amdgcn => .amdgcn, - .riscv32 => .riscv32, - .riscv64 => .riscv64, - .sparc => .sparc, - .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9. - .sparcel => .sparcel, - .s390x => .systemz, - .tce => .tce, - .tcele => .tcele, - .thumb => .thumb, - .thumbeb => .thumbeb, - .i386 => .x86, - .x86_64 => .x86_64, - .xcore => .xcore, - .nvptx => .nvptx, - .nvptx64 => .nvptx64, - .le32 => .le32, - .le64 => .le64, - .amdil => .amdil, - .amdil64 => .amdil64, - .hsail => .hsail, - .hsail64 => .hsail64, - .spir => .spir, - .spir64 => .spir64, - .kalimba => .kalimba, - .shave => .shave, - .lanai => .lanai, - .wasm32 => .wasm32, - .wasm64 => .wasm64, - .renderscript32 => .renderscript32, - .renderscript64 => .renderscript64, - .ve => .ve, - .spu_2, .spirv32, .spirv64 => .UnknownArch, - }; -} - fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool { if (ignore_case) { return std.ascii.eqlIgnoreCase(a, b); @@ -770,3 +669,10 @@ pub fn supportsFunctionAlignment(target: std.Target) bool { else => true, }; } + +pub fn supportsTailCall(target: std.Target, backend: std.builtin.CompilerBackend) bool { + switch (backend) { + .stage1, .stage2_llvm => return @import("codegen/llvm.zig").supportsTailCall(target), + else => return false, + } +} diff --git a/test/behavior/call.zig b/test/behavior/call.zig index 4a4ff4fb78..37c6ce3691 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -270,7 +270,12 @@ test "forced tail call" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_llvm) { + // Only attempt this test on targets we know have tail call support in LLVM. + if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) { + return error.SkipZigTest; + } + } const S = struct { fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 { @@ -298,7 +303,12 @@ test "inline call preserves tail call" { if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_llvm) { + // Only attempt this test on targets we know have tail call support in LLVM. + if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) { + return error.SkipZigTest; + } + } const max = std.math.maxInt(u16); const S = struct { -- cgit v1.2.3