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/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 a4e2106afb..0626fd30ee 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); @@ -6152,9 +6153,30 @@ 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 target = sema.mod.getTarget(); + 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)) { + 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 +6692,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 +6740,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 +6752,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( @@ -8177,8 +8203,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( @@ -8225,7 +8265,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 { @@ -8235,7 +8275,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( @@ -8244,6 +8294,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 @@ -8255,7 +8306,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, }; @@ -10459,7 +10510,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: { @@ -10583,7 +10639,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); } @@ -10615,7 +10674,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); } @@ -10633,6 +10692,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()) { @@ -15353,7 +15416,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, }; @@ -19262,7 +19325,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 +19335,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", .{}); @@ -19614,9 +19683,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); } @@ -21871,6 +21940,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: { @@ -21892,11 +21962,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); @@ -21928,7 +21997,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); @@ -21958,15 +22027,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); @@ -22002,7 +22071,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); @@ -22504,7 +22573,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, }; @@ -22516,14 +22585,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), @@ -22575,7 +22652,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 @@ -22691,7 +22768,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, }; @@ -22702,7 +22779,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); @@ -22828,7 +22905,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( @@ -22845,11 +22928,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); @@ -22886,6 +22974,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"); } @@ -22898,7 +22987,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); @@ -23050,9 +23145,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); @@ -23089,7 +23184,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()) { @@ -24050,7 +24145,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, }; @@ -24058,7 +24153,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, }; @@ -26793,7 +26888,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/codegen/llvm.zig b/src/codegen/llvm.zig index d1c68b430c..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, @@ -4522,7 +4632,7 @@ pub const FuncGen = struct { "", ); - if (return_type.isNoReturn()) { + if (return_type.isNoReturn() and attr != .AlwaysTail) { _ = self.builder.buildUnreachable(); return null; } @@ -8527,12 +8637,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 +8665,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 +8748,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/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/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/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 eafd2ef4e9..37c6ce3691 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -261,3 +261,71 @@ test "arguments to comptime parameters generated in comptime blocks" { }; S.foo(S.fortyTwo()); } + +test "forced tail call" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + 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_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + + 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 { + if (n == 0) return a; + if (n == 1) return b; + return @call( + .{ .modifier = .always_tail }, + fibonacciTailInternal, + .{ n - 1, b, a + b }, + ); + } + + fn fibonacciTail(n: u16) u16 { + return fibonacciTailInternal(n, 0, 1); + } + }; + try expect(S.fibonacciTail(10) == 55); +} + +test "inline call preserves tail call" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + 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_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + + 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 { + var a: u16 = 0; + fn foo() void { + return bar(); + } + + inline fn bar() void { + if (a == max) return; + // Stack overflow if not tail called + var buf: [max]u16 = undefined; + buf[a] = a; + a += 1; + return @call(.{ .modifier = .always_tail }, foo, .{}); + } + }; + S.foo(); + try expect(S.a == std.math.maxInt(u16)); +} 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); +} diff --git a/test/cases/compile_errors/invalid_tail_call.zig b/test/cases/compile_errors/invalid_tail_call.zig new file mode 100644 index 0000000000..cdeb9df930 --- /dev/null +++ b/test/cases/compile_errors/invalid_tail_call.zig @@ -0,0 +1,12 @@ +fn myFn(_: usize) void { + return; +} +pub export fn entry() void { + @call(.{ .modifier = .always_tail }, myFn, .{0}); +} + +// error +// backend=llvm +// target=native +// +// :5:5: error: unable to perform tail call: type of function being called 'fn(usize) void' does not match type of calling function 'fn() callconv(.C) void' 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 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 diff --git a/test/cases/taill_call_noreturn.zig b/test/cases/taill_call_noreturn.zig new file mode 100644 index 0000000000..fabb9e729b --- /dev/null +++ b/test/cases/taill_call_noreturn.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const builtin = std.builtin; +pub fn foo(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn { + @call(.{ .modifier = .always_tail }, bar, .{ message, stack_trace }); +} +pub fn bar(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn { + _ = message; + _ = stack_trace; + std.process.exit(0); +} + +pub fn main() void { + foo("foo", null); +} + +// run +// backend=llvm +// target=x86_64-linux,x86_64-macos,aarch64-linux,aarch64-macos