From 31a59c229cb39b9ffd1ee3397a1ce87c36b91477 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 26 Jul 2021 19:12:34 -0700 Subject: stage2: improvements towards `zig test` * Add AIR instruction: struct_field_val - This is part of an effort to eliminate the AIR instruction `ref`. - It's implemented for C backend and LLVM backend so far. * Rename `resolvePossiblyUndefinedValue` to `resolveMaybeUndefVal` just to save some columns on long lines. * Sema: add `fieldVal` alongside `fieldPtr` (renamed from `namedFieldPtr`). This is part of an effort to eliminate the AIR instruction `ref`. The idea is to avoid unnecessary loads, stores, stack usage, and IR instructions, by paying a DRY cost. LLVM backend improvements: * internal linkage vs exported linkage is implemented, along with aliases. There is an issue with incremental updates due to missing LLVM API for deleting aliases; see the relevant comment in this commit. - `updateDeclExports` is hooked up to the LLVM backend now. * Fix usage of `Type.tag() == .noreturn` rather than calling `isNoReturn()`. * Properly mark global variables as mutable/constant. * Fix llvm type generation of function pointers * Fix codegen for calls of function pointers * Implement llvm type generation of error unions and error sets. * Implement AIR instructions: addwrap, subwrap, mul, mulwrap, div, bit_and, bool_and, bit_or, bool_or, xor, struct_field_ptr, struct_field_val, unwrap_errunion_err, add for floats, sub for floats. After this commit, `zig test` on a file with `test "example" {}` correctly generates and executes a test binary. However the `test_functions` slice is undefined and just happens to be going into the .bss section, causing the length to be 0. The next step towards `zig test` will be replacing the `test_functions` Decl Value with the set of test function pointers, before it is sent to linker/codegen. --- src/Module.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index 8ebb1c203c..99f314c5cb 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3702,8 +3702,8 @@ pub fn analyzeExport( else => return mod.fail(scope, src, "unable to export type '{}'", .{exported_decl.ty}), } - try mod.decl_exports.ensureCapacity(mod.gpa, mod.decl_exports.count() + 1); - try mod.export_owners.ensureCapacity(mod.gpa, mod.export_owners.count() + 1); + try mod.decl_exports.ensureUnusedCapacity(mod.gpa, 1); + try mod.export_owners.ensureUnusedCapacity(mod.gpa, 1); const new_export = try mod.gpa.create(Export); errdefer mod.gpa.destroy(new_export); -- cgit v1.2.3 From a8e964eadd3496330043985cacaaee7db92886c6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jul 2021 14:06:42 -0700 Subject: stage2: `zig test` now works with the LLVM backend Frontend improvements: * When compiling in `zig test` mode, put a task on the work queue to analyze the main package root file. Normally, start code does `_ = import("root");` to make Zig analyze the user's code, however in the case of `zig test`, the root source file is the test runner. Without this change, no tests are picked up. * In the main pipeline, once semantic analysis is finished, if there are no compile errors, populate the `test_functions` Decl with the set of test functions picked up from semantic analysis. * Value: add `array` and `slice` Tags. LLVM backend improvements: * Fix incremental updates of globals. Previously the value of a global would not get replaced with a new value. * Fix LLVM type of arrays. They were incorrectly sending the ABI size as the element count. * Remove the FuncGen parameter from genTypedValue. This function is for generating global constants and there is no function available when it is being called. - The `ref_val` case is now commented out. I'd like to eliminate `ref_val` as one of the possible Value Tags. Instead it should always be done via `decl_ref`. * Implement constant value generation for slices, arrays, and structs. * Constant value generation for functions supports the `decl_ref` tag. --- src/Compilation.zig | 34 +++++------ src/Module.zig | 135 ++++++++++++++++++++++++++++++++++++++---- src/codegen/llvm.zig | 134 +++++++++++++++++++++++++++-------------- src/codegen/llvm/bindings.zig | 16 ++++- src/type.zig | 22 +++++++ src/value.zig | 73 +++++++++++++++++++++-- 6 files changed, 332 insertions(+), 82 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index a34d0cd198..00cdba85a4 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1709,7 +1709,9 @@ pub fn update(self: *Compilation) !void { // in the start code, but when using the stage1 backend that won't happen, // so in order to run AstGen on the root source file we put it into the // import_table here. - if (use_stage1) { + // Likewise, in the case of `zig test`, the test runner is the root source file, + // and so there is nothing to import the main file. + if (use_stage1 or self.bin_file.options.is_test) { _ = try module.importPkg(module.main_pkg); } @@ -1725,6 +1727,9 @@ pub fn update(self: *Compilation) !void { if (!use_stage1) { try self.work_queue.writeItem(.{ .analyze_pkg = std_pkg }); + if (self.bin_file.options.is_test) { + try self.work_queue.writeItem(.{ .analyze_pkg = module.main_pkg }); + } } } @@ -2053,24 +2058,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor assert(decl.has_tv); assert(decl.ty.hasCodeGenBits()); - self.bin_file.updateDecl(module, decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - decl.analysis = .codegen_failure; - continue; - }, - else => { - try module.failed_decls.ensureUnusedCapacity(gpa, 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create( - gpa, - decl.srcLoc(), - "unable to codegen: {s}", - .{@errorName(err)}, - )); - decl.analysis = .codegen_failure_retryable; - continue; - }, - }; + try module.linkerUpdateDecl(decl); }, }, .codegen_func => |func| switch (func.owner_decl.analysis) { @@ -2396,6 +2384,14 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor }; }, }; + + if (self.bin_file.options.is_test and self.totalErrorCount() == 0) { + // The `test_functions` decl has been intentionally postponed until now, + // at which point we must populate it with the list of test functions that + // have been discovered and not filtered out. + const mod = self.bin_file.options.module.?; + try mod.populateTestFunctions(); + } } const AstGenSrc = union(enum) { diff --git a/src/Module.zig b/src/Module.zig index 99f314c5cb..4ddce33655 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -112,6 +112,8 @@ compile_log_text: ArrayListUnmanaged(u8) = .{}, emit_h: ?*GlobalEmitH, +test_functions: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{}, + /// A `Module` has zero or one of these depending on whether `-femit-h` is enabled. pub const GlobalEmitH = struct { /// Where to put the output. @@ -282,6 +284,7 @@ pub const Decl = struct { pub fn destroy(decl: *Decl, module: *Module) void { const gpa = module.gpa; log.debug("destroy {*} ({s})", .{ decl, decl.name }); + _ = module.test_functions.swapRemove(decl); if (decl.deletion_flag) { assert(module.deletion_set.swapRemove(decl)); } @@ -3319,6 +3322,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi // the test name filter. if (!mod.comp.bin_file.options.is_test) break :blk false; if (decl_pkg != mod.main_pkg) break :blk false; + try mod.test_functions.put(gpa, new_decl, {}); break :blk true; }, else => blk: { @@ -3326,6 +3330,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi if (!mod.comp.bin_file.options.is_test) break :blk false; if (decl_pkg != mod.main_pkg) break :blk false; // TODO check the name against --test-filter + try mod.test_functions.put(gpa, new_decl, {}); break :blk true; }, }; @@ -3765,17 +3770,38 @@ pub fn createAnonymousDeclNamed( scope: *Scope, typed_value: TypedValue, name: [:0]u8, +) !*Decl { + return mod.createAnonymousDeclFromDeclNamed(scope.ownerDecl().?, typed_value, name); +} + +pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl { + return mod.createAnonymousDeclFromDecl(scope.ownerDecl().?, typed_value); +} + +pub fn createAnonymousDeclFromDecl(mod: *Module, owner_decl: *Decl, tv: TypedValue) !*Decl { + const name_index = mod.getNextAnonNameIndex(); + const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{ + owner_decl.name, name_index, + }); + return mod.createAnonymousDeclFromDeclNamed(owner_decl, tv, name); +} + +/// Takes ownership of `name` even if it returns an error. +pub fn createAnonymousDeclFromDeclNamed( + mod: *Module, + owner_decl: *Decl, + typed_value: TypedValue, + name: [:0]u8, ) !*Decl { errdefer mod.gpa.free(name); - const scope_decl = scope.ownerDecl().?; - const namespace = scope_decl.namespace; + const namespace = owner_decl.namespace; try namespace.anon_decls.ensureUnusedCapacity(mod.gpa, 1); - const new_decl = try mod.allocateNewDecl(namespace, scope_decl.src_node); + const new_decl = try mod.allocateNewDecl(namespace, owner_decl.src_node); new_decl.name = name; - new_decl.src_line = scope_decl.src_line; + new_decl.src_line = owner_decl.src_line; new_decl.ty = typed_value.ty; new_decl.val = typed_value.val; new_decl.has_tv = true; @@ -3796,15 +3822,6 @@ pub fn createAnonymousDeclNamed( return new_decl; } -pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl { - const scope_decl = scope.ownerDecl().?; - const name_index = mod.getNextAnonNameIndex(); - const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{ - scope_decl.name, name_index, - }); - return mod.createAnonymousDeclNamed(scope, typed_value, name); -} - pub fn getNextAnonNameIndex(mod: *Module) usize { return @atomicRmw(usize, &mod.next_anon_name_index, .Add, 1, .Monotonic); } @@ -4801,3 +4818,95 @@ pub fn processExports(mod: *Module) !void { }; } } + +pub fn populateTestFunctions(mod: *Module) !void { + const gpa = mod.gpa; + const builtin_pkg = mod.main_pkg.table.get("builtin").?; + const builtin_file = (mod.importPkg(builtin_pkg) catch unreachable).file; + const builtin_namespace = builtin_file.root_decl.?.namespace; + const decl = builtin_namespace.decls.get("test_functions").?; + var buf: Type.Payload.ElemType = undefined; + const tmp_test_fn_ty = decl.ty.slicePtrFieldType(&buf).elemType(); + + const array_decl = d: { + // Add mod.test_functions to an array decl then make the test_functions + // decl reference it as a slice. + var new_decl_arena = std.heap.ArenaAllocator.init(gpa); + errdefer new_decl_arena.deinit(); + const arena = &new_decl_arena.allocator; + + const test_fn_vals = try arena.alloc(Value, mod.test_functions.count()); + const array_decl = try mod.createAnonymousDeclFromDecl(decl, .{ + .ty = try Type.Tag.array.create(arena, .{ + .len = test_fn_vals.len, + .elem_type = try tmp_test_fn_ty.copy(arena), + }), + .val = try Value.Tag.array.create(arena, test_fn_vals), + }); + for (mod.test_functions.keys()) |test_decl, i| { + const test_name_slice = mem.sliceTo(test_decl.name, 0); + const test_name_decl = n: { + var name_decl_arena = std.heap.ArenaAllocator.init(gpa); + errdefer name_decl_arena.deinit(); + const bytes = try name_decl_arena.allocator.dupe(u8, test_name_slice); + const test_name_decl = try mod.createAnonymousDeclFromDecl(array_decl, .{ + .ty = try Type.Tag.array_u8.create(&name_decl_arena.allocator, bytes.len), + .val = try Value.Tag.bytes.create(&name_decl_arena.allocator, bytes), + }); + try test_name_decl.finalizeNewArena(&name_decl_arena); + break :n test_name_decl; + }; + try mod.linkerUpdateDecl(test_name_decl); + + const field_vals = try arena.create([3]Value); + field_vals.* = .{ + try Value.Tag.slice.create(arena, .{ + .ptr = try Value.Tag.decl_ref.create(arena, test_name_decl), + .len = try Value.Tag.int_u64.create(arena, test_name_slice.len), + }), // name + try Value.Tag.decl_ref.create(arena, test_decl), // func + Value.initTag(.null_value), // async_frame_size + }; + test_fn_vals[i] = try Value.Tag.@"struct".create(arena, field_vals); + } + + try array_decl.finalizeNewArena(&new_decl_arena); + break :d array_decl; + }; + try mod.linkerUpdateDecl(array_decl); + + { + var arena_instance = decl.value_arena.?.promote(gpa); + defer decl.value_arena.?.* = arena_instance.state; + const arena = &arena_instance.allocator; + + decl.ty = try Type.Tag.const_slice.create(arena, try tmp_test_fn_ty.copy(arena)); + decl.val = try Value.Tag.slice.create(arena, .{ + .ptr = try Value.Tag.decl_ref.create(arena, array_decl), + .len = try Value.Tag.int_u64.create(arena, mod.test_functions.count()), + }); + } + try mod.linkerUpdateDecl(decl); +} + +pub fn linkerUpdateDecl(mod: *Module, decl: *Decl) !void { + mod.comp.bin_file.updateDecl(mod, decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + decl.analysis = .codegen_failure; + return; + }, + else => { + const gpa = mod.gpa; + try mod.failed_decls.ensureUnusedCapacity(gpa, 1); + mod.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + gpa, + decl.srcLoc(), + "unable to codegen: {s}", + .{@errorName(err)}, + )); + decl.analysis = .codegen_failure_retryable; + return; + }, + }; +} diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 1b3c36fc69..2468b1dbde 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -500,7 +500,18 @@ pub const DeclGen = struct { } else if (decl.val.castTag(.extern_fn)) |extern_fn| { _ = try self.resolveLlvmFunction(extern_fn.data); } else { - _ = try self.resolveGlobalDecl(decl); + const global = try self.resolveGlobalDecl(decl); + assert(decl.has_tv); + const init_val = if (decl.val.castTag(.variable)) |payload| init_val: { + const variable = payload.data; + break :init_val variable.init; + } else init_val: { + global.setGlobalConstant(.True); + break :init_val decl.val; + }; + + const llvm_init = try self.genTypedValue(.{ .ty = decl.ty, .val = init_val }); + llvm.setInitializer(global, llvm_init); } } @@ -548,25 +559,11 @@ pub const DeclGen = struct { } fn resolveGlobalDecl(self: *DeclGen, decl: *Module.Decl) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - if (self.llvmModule().getNamedGlobal(decl.name)) |val| return val; - - assert(decl.has_tv); - + const llvm_module = self.object.llvm_module; + if (llvm_module.getNamedGlobal(decl.name)) |val| return val; // TODO: remove this redundant `llvmType`, it is also called in `genTypedValue`. const llvm_type = try self.llvmType(decl.ty); - const global = self.llvmModule().addGlobal(llvm_type, decl.name); - const init_val = if (decl.val.castTag(.variable)) |payload| init_val: { - const variable = payload.data; - break :init_val variable.init; - } else init_val: { - global.setGlobalConstant(.True); - break :init_val decl.val; - }; - - const llvm_init = try self.genTypedValue(.{ .ty = decl.ty, .val = init_val }, null); - llvm.setInitializer(global, llvm_init); - - return global; + return llvm_module.addGlobal(llvm_type, decl.name); } fn llvmType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type { @@ -596,7 +593,8 @@ pub const DeclGen = struct { }, .Array => { const elem_type = try self.llvmType(t.elemType()); - return elem_type.arrayType(@intCast(c_uint, t.abiSize(self.module.getTarget()))); + const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null); + return elem_type.arrayType(@intCast(c_uint, total_len)); }, .Optional => { if (!t.isPtrLikeOptional()) { @@ -674,8 +672,7 @@ pub const DeclGen = struct { } } - // TODO: figure out a way to remove the FuncGen argument - fn genTypedValue(self: *DeclGen, tv: TypedValue, fg: ?*FuncGen) error{ OutOfMemory, CodegenFail }!*const llvm.Value { + fn genTypedValue(self: *DeclGen, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { const llvm_type = try self.llvmType(tv.ty); if (tv.val.isUndef()) @@ -711,20 +708,36 @@ pub const DeclGen = struct { usize_type.constNull(), }; - // TODO: consider using buildInBoundsGEP2 for opaque pointers - return fg.?.builder.buildInBoundsGEP(val, &indices, 2, ""); + return val.constInBoundsGEP(&indices, indices.len); }, .ref_val => { - const elem_value = tv.val.castTag(.ref_val).?.data; - const elem_type = tv.ty.castPointer().?.data; - const alloca = fg.?.buildAlloca(try self.llvmType(elem_type)); - _ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca); - return alloca; + //const elem_value = tv.val.castTag(.ref_val).?.data; + //const elem_type = tv.ty.castPointer().?.data; + //const alloca = fg.?.buildAlloca(try self.llvmType(elem_type)); + //_ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca); + //return alloca; + // TODO eliminate the ref_val Value Tag + return self.todo("implement const of pointer tag ref_val", .{}); }, .variable => { const variable = tv.val.castTag(.variable).?.data; return self.resolveGlobalDecl(variable.owner_decl); }, + .slice => { + const slice = tv.val.castTag(.slice).?.data; + var buf: Type.Payload.ElemType = undefined; + const fields: [2]*const llvm.Value = .{ + try self.genTypedValue(.{ + .ty = tv.ty.slicePtrFieldType(&buf), + .val = slice.ptr, + }), + try self.genTypedValue(.{ + .ty = Type.initTag(.usize), + .val = slice.len, + }), + }; + return self.context.constStruct(&fields, fields.len, .False); + }, else => |tag| return self.todo("implement const of pointer type '{}' ({})", .{ tv.ty, tag }), }, .Array => { @@ -734,10 +747,28 @@ pub const DeclGen = struct { return self.todo("handle other sentinel values", .{}); } else false; - return self.context.constString(payload.data.ptr, @intCast(c_uint, payload.data.len), llvm.Bool.fromBool(!zero_sentinel)); - } else { - return self.todo("handle more array values", .{}); + return self.context.constString( + payload.data.ptr, + @intCast(c_uint, payload.data.len), + llvm.Bool.fromBool(!zero_sentinel), + ); + } + if (tv.val.castTag(.array)) |payload| { + const gpa = self.gpa; + const elem_ty = tv.ty.elemType(); + const elem_vals = payload.data; + const llvm_elems = try gpa.alloc(*const llvm.Value, elem_vals.len); + defer gpa.free(llvm_elems); + for (elem_vals) |elem_val, i| { + llvm_elems[i] = try self.genTypedValue(.{ .ty = elem_ty, .val = elem_val }); + } + const llvm_elem_ty = try self.llvmType(elem_ty); + return llvm_elem_ty.constArray( + llvm_elems.ptr, + @intCast(c_uint, llvm_elems.len), + ); } + return self.todo("handle more array values", .{}); }, .Optional => { if (!tv.ty.isPtrLikeOptional()) { @@ -750,26 +781,25 @@ pub const DeclGen = struct { llvm_child_type.constNull(), self.context.intType(1).constNull(), }; - return self.context.constStruct(&optional_values, 2, .False); + return self.context.constStruct(&optional_values, optional_values.len, .False); } else { var optional_values: [2]*const llvm.Value = .{ - try self.genTypedValue(.{ .ty = child_type, .val = tv.val }, fg), + try self.genTypedValue(.{ .ty = child_type, .val = tv.val }), self.context.intType(1).constAllOnes(), }; - return self.context.constStruct(&optional_values, 2, .False); + return self.context.constStruct(&optional_values, optional_values.len, .False); } } else { return self.todo("implement const of optional pointer", .{}); } }, .Fn => { - const fn_decl = if (tv.val.castTag(.extern_fn)) |extern_fn| - extern_fn.data - else if (tv.val.castTag(.function)) |func_payload| - func_payload.data.owner_decl - else - unreachable; - + const fn_decl = switch (tv.val.tag()) { + .extern_fn => tv.val.castTag(.extern_fn).?.data, + .function => tv.val.castTag(.function).?.data.owner_decl, + .decl_ref => tv.val.castTag(.decl_ref).?.data, + else => unreachable, + }; return self.resolveLlvmFunction(fn_decl); }, .ErrorSet => { @@ -793,11 +823,29 @@ pub const DeclGen = struct { if (!payload_type.hasCodeGenBits()) { // We use the error type directly as the type. - return self.genTypedValue(.{ .ty = error_type, .val = sub_val }, fg); + return self.genTypedValue(.{ .ty = error_type, .val = sub_val }); } return self.todo("implement error union const of type '{}'", .{tv.ty}); }, + .Struct => { + const fields_len = tv.ty.structFieldCount(); + const field_vals = tv.val.castTag(.@"struct").?.data; + const gpa = self.gpa; + const llvm_fields = try gpa.alloc(*const llvm.Value, fields_len); + defer gpa.free(llvm_fields); + for (llvm_fields) |*llvm_field, i| { + llvm_field.* = try self.genTypedValue(.{ + .ty = tv.ty.structFieldType(i), + .val = field_vals[i], + }); + } + return self.context.constStruct( + llvm_fields.ptr, + @intCast(c_uint, llvm_fields.len), + .False, + ); + }, else => return self.todo("implement const of type '{}'", .{tv.ty}), } } @@ -869,7 +917,7 @@ pub const FuncGen = struct { fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*const llvm.Value { if (self.air.value(inst)) |val| { - return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }, self); + return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }); } const inst_index = Air.refToIndex(inst).?; if (self.func_inst_table.get(inst_index)) |value| return value; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 9ef6d7e1ac..d63a75d8bd 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -49,7 +49,12 @@ pub const Context = opaque { extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: Bool) *const Value; pub const constStruct = LLVMConstStructInContext; - extern fn LLVMConstStructInContext(C: *const Context, ConstantVals: [*]*const Value, Count: c_uint, Packed: Bool) *const Value; + extern fn LLVMConstStructInContext( + C: *const Context, + ConstantVals: [*]const *const Value, + Count: c_uint, + Packed: Bool, + ) *const Value; pub const createBasicBlock = LLVMCreateBasicBlockInContext; extern fn LLVMCreateBasicBlockInContext(C: *const Context, Name: [*:0]const u8) *const BasicBlock; @@ -100,6 +105,13 @@ pub const Value = opaque { pub const setAliasee = LLVMAliasSetAliasee; extern fn LLVMAliasSetAliasee(Alias: *const Value, Aliasee: *const Value) void; + + pub const constInBoundsGEP = LLVMConstInBoundsGEP; + extern fn LLVMConstInBoundsGEP( + ConstantVal: *const Value, + ConstantIndices: [*]const *const Value, + NumIndices: c_uint, + ) *const Value; }; pub const Type = opaque { @@ -113,7 +125,7 @@ pub const Type = opaque { extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value; pub const constArray = LLVMConstArray; - extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: ?[*]*const Value, Length: c_uint) *const Value; + extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: [*]*const Value, Length: c_uint) *const Value; pub const getUndef = LLVMGetUndef; extern fn LLVMGetUndef(Ty: *const Type) *const Value; diff --git a/src/type.zig b/src/type.zig index 731c69b806..4dd1a15fdd 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1526,6 +1526,8 @@ pub const Type = extern union { .var_args_param => unreachable, .@"struct" => { + const s = self.castTag(.@"struct").?.data; + assert(s.status == .have_layout); @panic("TODO abiSize struct"); }, .enum_simple, .enum_full, .enum_nonexhaustive => { @@ -2768,6 +2770,26 @@ pub const Type = extern union { } } + pub fn structFieldCount(ty: Type) usize { + switch (ty.tag()) { + .@"struct" => { + const struct_obj = ty.castTag(.@"struct").?.data; + return struct_obj.fields.count(); + }, + else => unreachable, + } + } + + pub fn structFieldType(ty: Type, index: usize) Type { + switch (ty.tag()) { + .@"struct" => { + const struct_obj = ty.castTag(.@"struct").?.data; + return struct_obj.fields.values()[index].ty; + }, + else => unreachable, + } + } + pub fn declSrcLoc(ty: Type) Module.SrcLoc { switch (ty.tag()) { .enum_full, .enum_nonexhaustive => { diff --git a/src/value.zig b/src/value.zig index abb2ea7b1e..fc34473921 100644 --- a/src/value.zig +++ b/src/value.zig @@ -112,6 +112,10 @@ pub const Value = extern union { /// This value is repeated some number of times. The amount of times to repeat /// is stored externally. repeated, + /// Each element stored as a `Value`. + array, + /// Pointer and length as sub `Value` objects. + slice, float_16, float_32, float_64, @@ -217,6 +221,9 @@ pub const Value = extern union { .enum_literal, => Payload.Bytes, + .array => Payload.Array, + .slice => Payload.Slice, + .enum_field_index => Payload.U32, .ty => Payload.Ty, @@ -442,6 +449,28 @@ pub const Value = extern union { }; return Value{ .ptr_otherwise = &new_payload.base }; }, + .array => { + const payload = self.castTag(.array).?; + const new_payload = try allocator.create(Payload.Array); + new_payload.* = .{ + .base = payload.base, + .data = try allocator.alloc(Value, payload.data.len), + }; + std.mem.copy(Value, new_payload.data, payload.data); + return Value{ .ptr_otherwise = &new_payload.base }; + }, + .slice => { + const payload = self.castTag(.slice).?; + const new_payload = try allocator.create(Payload.Slice); + new_payload.* = .{ + .base = payload.base, + .data = .{ + .ptr = try payload.data.ptr.copy(allocator), + .len = try payload.data.len.copy(allocator), + }, + }; + return Value{ .ptr_otherwise = &new_payload.base }; + }, .float_16 => return self.copyPayloadShallow(allocator, Payload.Float_16), .float_32 => return self.copyPayloadShallow(allocator, Payload.Float_32), .float_64 => return self.copyPayloadShallow(allocator, Payload.Float_64), @@ -605,6 +634,8 @@ pub const Value = extern union { try out_stream.writeAll("(repeated) "); val = val.castTag(.repeated).?.data; }, + .array => return out_stream.writeAll("(array)"), + .slice => return out_stream.writeAll("(slice)"), .float_16 => return out_stream.print("{}", .{val.castTag(.float_16).?.data}), .float_32 => return out_stream.print("{}", .{val.castTag(.float_32).?.data}), .float_64 => return out_stream.print("{}", .{val.castTag(.float_64).?.data}), @@ -729,6 +760,8 @@ pub const Value = extern union { .field_ptr, .bytes, .repeated, + .array, + .slice, .float_16, .float_32, .float_64, @@ -1075,6 +1108,8 @@ pub const Value = extern union { return orderAgainstZero(lhs).compare(op); } + /// TODO we can't compare value equality without also knowing the type to treat + /// the values as pub fn eql(a: Value, b: Value) bool { const a_tag = a.tag(); const b_tag = b.tag(); @@ -1109,6 +1144,8 @@ pub const Value = extern union { return @truncate(u32, self.hash()); } + /// TODO we can't hash without also knowing the type of the value. + /// we have to hash as if there were a canonical value memory layout. pub fn hash(self: Value) u64 { var hasher = std.hash.Wyhash.init(0); @@ -1203,6 +1240,15 @@ pub const Value = extern union { const payload = self.castTag(.bytes).?; hasher.update(payload.data); }, + .repeated => { + @panic("TODO Value.hash for repeated"); + }, + .array => { + @panic("TODO Value.hash for array"); + }, + .slice => { + @panic("TODO Value.hash for slice"); + }, .int_u64 => { const payload = self.castTag(.int_u64).?; std.hash.autoHash(&hasher, payload.data); @@ -1211,10 +1257,6 @@ pub const Value = extern union { const payload = self.castTag(.int_i64).?; std.hash.autoHash(&hasher, payload.data); }, - .repeated => { - const payload = self.castTag(.repeated).?; - std.hash.autoHash(&hasher, payload.data.hash()); - }, .ref_val => { const payload = self.castTag(.ref_val).?; std.hash.autoHash(&hasher, payload.data.hash()); @@ -1340,6 +1382,8 @@ pub const Value = extern union { return switch (val.tag()) { .empty_array => 0, .bytes => val.castTag(.bytes).?.data.len, + .array => val.castTag(.array).?.data.len, + .slice => val.castTag(.slice).?.data.len.toUnsignedInt(), .ref_val => sliceLen(val.castTag(.ref_val).?.data), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; @@ -1364,6 +1408,9 @@ pub const Value = extern union { // No matter the index; all the elements are the same! .repeated => return self.castTag(.repeated).?.data, + .array => return self.castTag(.array).?.data[index], + .slice => return self.castTag(.slice).?.data.ptr.elemValue(allocator, index), + else => unreachable, } } @@ -1450,7 +1497,8 @@ pub const Value = extern union { } /// Valid for all types. Asserts the value is not undefined. - pub fn isType(self: Value) bool { + /// TODO this function is a code smell and should be deleted + fn isType(self: Value) bool { return switch (self.tag()) { .ty, .int_type, @@ -1528,6 +1576,8 @@ pub const Value = extern union { .field_ptr, .bytes, .repeated, + .array, + .slice, .float_16, .float_32, .float_64, @@ -1638,6 +1688,19 @@ pub const Value = extern union { data: []const u8, }; + pub const Array = struct { + base: Payload, + data: []Value, + }; + + pub const Slice = struct { + base: Payload, + data: struct { + ptr: Value, + len: Value, + }, + }; + pub const Ty = struct { base: Payload, data: Type, -- cgit v1.2.3 From a2eb91c42294ef380d70742e1111e4dc36ab2192 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jul 2021 15:44:21 -0700 Subject: stage2: add deinit for test_functions --- src/Module.zig | 1 + 1 file changed, 1 insertion(+) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index 4ddce33655..48c2eb8d0d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2123,6 +2123,7 @@ pub fn deinit(mod: *Module) void { mod.global_error_set.deinit(gpa); mod.error_name_list.deinit(gpa); + mod.test_functions.deinit(gpa); } fn freeExportList(gpa: *Allocator, export_list: []*Export) void { -- cgit v1.2.3 From a5c6e51f03ab164e64b1a1d8370071dd1e670458 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jul 2021 15:59:51 -0700 Subject: stage2: more principled approach to comptime references * AIR no longer has a `variables` array. Instead of the `varptr` instruction, Sema emits a constant with a `decl_ref`. * AIR no longer has a `ref` instruction. There is no longer any instruction that takes a value and returns a pointer to it. If this is desired, Sema must either create an anynomous Decl and return a constant `decl_ref`, or in the case of a runtime value, emit an `alloc` instruction, `store` the value to it, and then return the `alloc`. * The `ref_val` Value Tag is eliminated. `decl_ref` should be used instead. Also added is `eu_payload_ptr` which points to the payload of an error union, given an error union pointer. In general, Sema should avoid calling `analyzeRef` if it can be helped. For example in the case of field_val and elem_val, there should never be a reason to create a temporary (alloc or decl). Recent previous commits made progress along that front. There is a new abstraction in Sema, which looks like this: var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); // here 'anon_decl.arena()` may be used const decl = try anon_decl.finish(ty, val); // decl is typically now used with `decl_ref`. This pattern is used to upgrade `ref_val` usages to `decl_ref` usages. Additional improvements: * Sema: fix source location resolution for calling convention expression. * Sema: properly report "unable to resolve comptime value" for loads of global variables. There is now a set of functions which can be called if the callee wants to obtain the Value even if the tag is `variable` (indicating comptime-known address but runtime-known value). * Sema: `coerce` resolves builtin types before checking equality. * Sema: fix `u1_type` missing from `addType`, making this type have a slightly more efficient representation in AIR. * LLVM backend: fix `genTypedValue` for tags `decl_ref` and `variable` to properly do an LLVMConstBitCast. * Remove unused parameter from `Value.toEnum`. After this commit, some test cases are no longer passing. This is due to the more principled approach to comptime references causing more anonymous decls to get sent to the linker for codegen. However, in all these cases the decls are not actually referenced by the runtime machine code. A future commit in this branch will implement garbage collection of decls so that unused decls do not get sent to the linker for codegen. This will make the tests go back to passing. --- src/Air.zig | 14 ---- src/Liveness.zig | 2 - src/Module.zig | 47 ++++++++++- src/Sema.zig | 191 ++++++++++++++++++++++++++++-------------- src/codegen.zig | 41 --------- src/codegen/c.zig | 41 +-------- src/codegen/llvm.zig | 35 +------- src/codegen/llvm/bindings.zig | 3 + src/codegen/wasm.zig | 59 ++++++------- src/link/Wasm.zig | 2 +- src/print_air.zig | 13 +-- src/value.zig | 71 +++++++--------- test/cases.zig | 5 +- 13 files changed, 246 insertions(+), 278 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Air.zig b/src/Air.zig index bd7b3af733..fb95d60d00 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -15,7 +15,6 @@ instructions: std.MultiArrayList(Inst).Slice, /// The first few indexes are reserved. See `ExtraIndex` for the values. extra: []const u32, values: []const Value, -variables: []const *Module.Var, pub const ExtraIndex = enum(u32) { /// Payload index of the main `Block` in the `extra` array. @@ -193,20 +192,10 @@ pub const Inst = struct { /// Result type is always `u1`. /// Uses the `un_op` field. bool_to_int, - /// Stores a value onto the stack and returns a pointer to it. - /// TODO audit where this AIR instruction is emitted, maybe it should instead be emitting - /// alloca instruction and storing to the alloca. - /// Uses the `ty_op` field. - ref, /// Return a value from a function. /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `un_op` field. ret, - /// Returns a pointer to a global variable. - /// Uses the `ty_pl` field. Index is into the `variables` array. - /// TODO this can be modeled simply as a constant with a decl ref and then - /// the variables array can be removed from Air. - varptr, /// Write a value to a pointer. LHS is pointer, RHS is value. /// Result type is always void. /// Uses the `bin_op` field. @@ -454,7 +443,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .assembly, .block, .constant, - .varptr, .struct_field_ptr, .struct_field_val, => return air.getRefType(datas[inst].ty_pl.ty), @@ -462,7 +450,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .not, .bitcast, .load, - .ref, .floatcast, .intcast, .optional_payload, @@ -550,7 +537,6 @@ pub fn deinit(air: *Air, gpa: *std.mem.Allocator) void { air.instructions.deinit(gpa); gpa.free(air.extra); gpa.free(air.values); - gpa.free(air.variables); air.* = undefined; } diff --git a/src/Liveness.zig b/src/Liveness.zig index a44b582424..7ba062fa31 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -256,14 +256,12 @@ fn analyzeInst( .const_ty, .breakpoint, .dbg_stmt, - .varptr, .unreach, => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }), .not, .bitcast, .load, - .ref, .floatcast, .intcast, .optional_payload, diff --git a/src/Module.zig b/src/Module.zig index 48c2eb8d0d..f89f72bc65 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1324,6 +1324,42 @@ pub const Scope = struct { block.instructions.appendAssumeCapacity(result_index); return result_index; } + + pub fn startAnonDecl(block: *Block) !WipAnonDecl { + return WipAnonDecl{ + .block = block, + .new_decl_arena = std.heap.ArenaAllocator.init(block.sema.gpa), + .finished = false, + }; + } + + pub const WipAnonDecl = struct { + block: *Scope.Block, + new_decl_arena: std.heap.ArenaAllocator, + finished: bool, + + pub fn arena(wad: *WipAnonDecl) *Allocator { + return &wad.new_decl_arena.allocator; + } + + pub fn deinit(wad: *WipAnonDecl) void { + if (!wad.finished) { + wad.new_decl_arena.deinit(); + } + wad.* = undefined; + } + + pub fn finish(wad: *WipAnonDecl, ty: Type, val: Value) !*Decl { + const new_decl = try wad.block.sema.mod.createAnonymousDecl(&wad.block.base, .{ + .ty = ty, + .val = val, + }); + errdefer wad.block.sema.mod.deleteAnonDecl(&wad.block.base, new_decl); + try new_decl.finalizeNewArena(&wad.new_decl_arena); + wad.finished = true; + return new_decl; + } + }; }; }; @@ -1700,6 +1736,7 @@ pub const SrcLoc = struct { .node_offset_fn_type_cc => |node_off| { const tree = try src_loc.file_scope.getTree(gpa); + const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = src_loc.declRelativeToNodeIndex(node_off); var params: [1]ast.Node.Index = undefined; @@ -1708,6 +1745,13 @@ pub const SrcLoc = struct { .fn_proto_multi => tree.fnProtoMulti(node), .fn_proto_one => tree.fnProtoOne(¶ms, node), .fn_proto => tree.fnProto(node), + .fn_decl => switch (node_tags[node_datas[node].lhs]) { + .fn_proto_simple => tree.fnProtoSimple(¶ms, node_datas[node].lhs), + .fn_proto_multi => tree.fnProtoMulti(node_datas[node].lhs), + .fn_proto_one => tree.fnProtoOne(¶ms, node_datas[node].lhs), + .fn_proto => tree.fnProto(node_datas[node].lhs), + else => unreachable, + }, else => unreachable, }; const main_tokens = tree.nodes.items(.main_token); @@ -2935,7 +2979,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { const break_index = try sema.analyzeBody(&block_scope, body); const result_ref = zir_datas[break_index].@"break".operand; const src: LazySrcLoc = .{ .node_offset = 0 }; - const decl_tv = try sema.resolveInstConst(&block_scope, src, result_ref); + const decl_tv = try sema.resolveInstValue(&block_scope, src, result_ref); const align_val = blk: { const align_ref = decl.zirAlignRef(); if (align_ref == .none) break :blk Value.initTag(.null_value); @@ -3603,7 +3647,6 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { .instructions = sema.air_instructions.toOwnedSlice(), .extra = sema.air_extra.toOwnedSlice(gpa), .values = sema.air_values.toOwnedSlice(gpa), - .variables = sema.air_variables.toOwnedSlice(gpa), }; } diff --git a/src/Sema.zig b/src/Sema.zig index 88a83fd661..95baae7e92 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14,7 +14,6 @@ code: Zir, air_instructions: std.MultiArrayList(Air.Inst) = .{}, air_extra: std.ArrayListUnmanaged(u32) = .{}, air_values: std.ArrayListUnmanaged(Value) = .{}, -air_variables: std.ArrayListUnmanaged(*Module.Var) = .{}, /// Maps ZIR to AIR. inst_map: InstMap = .{}, /// When analyzing an inline function call, owner_decl is the Decl of the caller @@ -76,7 +75,6 @@ pub fn deinit(sema: *Sema) void { sema.air_instructions.deinit(gpa); sema.air_extra.deinit(gpa); sema.air_values.deinit(gpa); - sema.air_variables.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); sema.* = undefined; @@ -639,16 +637,40 @@ fn analyzeAsType( return val.toType(sema.arena); } +/// May return Value Tags: `variable`, `undef`. +/// See `resolveConstValue` for an alternative. +fn resolveValue( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + air_ref: Air.Inst.Ref, +) CompileError!Value { + if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| { + return val; + } + return sema.failWithNeededComptime(block, src); +} + +/// Will not return Value Tags: `variable`, `undef`. Instead they will emit compile errors. +/// See `resolveValue` for an alternative. fn resolveConstValue( sema: *Sema, block: *Scope.Block, src: LazySrcLoc, air_ref: Air.Inst.Ref, ) CompileError!Value { - return (try sema.resolveDefinedValue(block, src, air_ref)) orelse - return sema.failWithNeededComptime(block, src); + if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| { + switch (val.tag()) { + .undef => return sema.failWithUseOfUndef(block, src), + .variable => return sema.failWithNeededComptime(block, src), + else => return val, + } + } + return sema.failWithNeededComptime(block, src); } +/// Value Tag `variable` causes this function to return `null`. +/// Value Tag `undef` causes this function to return a compile error. fn resolveDefinedValue( sema: *Sema, block: *Scope.Block, @@ -664,11 +686,27 @@ fn resolveDefinedValue( return null; } +/// Value Tag `variable` causes this function to return `null`. +/// Value Tag `undef` causes this function to return the Value. fn resolveMaybeUndefVal( sema: *Sema, block: *Scope.Block, src: LazySrcLoc, inst: Air.Inst.Ref, +) CompileError!?Value { + const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; + if (val.tag() == .variable) { + return sema.failWithNeededComptime(block, src); + } + return val; +} + +/// Returns all Value tags including `variable` and `undef`. +fn resolveMaybeUndefValAllowVariables( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + inst: Air.Inst.Ref, ) CompileError!?Value { // First section of indexes correspond to a set number of constant values. var i: usize = @enumToInt(inst); @@ -734,6 +772,8 @@ fn resolveInt( return val.toUnsignedInt(); } +// Returns a compile error if the value has tag `variable`. See `resolveInstValue` for +// a function that does not. pub fn resolveInstConst( sema: *Sema, block: *Scope.Block, @@ -748,6 +788,22 @@ pub fn resolveInstConst( }; } +// Value Tag may be `undef` or `variable`. +// See `resolveInstConst` for an alternative. +pub fn resolveInstValue( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + zir_ref: Zir.Inst.Ref, +) CompileError!TypedValue { + const air_ref = sema.resolveInst(zir_ref); + const val = try sema.resolveValue(block, src, air_ref); + return TypedValue{ + .ty = sema.typeOf(air_ref), + .val = val, + }; +} + fn zirBitcastResultPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); @@ -1707,7 +1763,7 @@ fn zirStr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A }); errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); try new_decl.finalizeNewArena(&new_decl_arena); - return sema.analyzeDeclRef(block, .unneeded, new_decl); + return sema.analyzeDeclRef(new_decl); } fn zirInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -2090,10 +2146,7 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro const linkage_index = struct_obj.fields.getIndex("linkage").?; const section_index = struct_obj.fields.getIndex("section").?; const export_name = try fields[name_index].toAllocatedBytes(sema.arena); - const linkage = fields[linkage_index].toEnum( - struct_obj.fields.values()[linkage_index].ty, - std.builtin.GlobalLinkage, - ); + const linkage = fields[linkage_index].toEnum(std.builtin.GlobalLinkage); if (linkage != .Strong) { return sema.mod.fail(&block.base, src, "TODO: implement exporting with non-strong linkage", .{}); @@ -2194,7 +2247,7 @@ fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const src = inst_data.src(); const decl_name = inst_data.get(sema.code); const decl = try sema.lookupIdentifier(block, src, decl_name); - return sema.analyzeDeclRef(block, src, decl); + return sema.analyzeDeclRef(decl); } fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -2978,14 +3031,9 @@ fn zirErrUnionPayloadPtr( if (val.getError()) |name| { return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); } - const data = val.castTag(.error_union).?.data; - // The same Value represents the pointer to the error union and the payload. return sema.addConstant( operand_pointer_ty, - try Value.Tag.ref_val.create( - sema.arena, - data, - ), + try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), ); } @@ -6296,7 +6344,7 @@ fn zirFuncExtended( const cc_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; const cc_tv = try sema.resolveInstConst(block, cc_src, cc_ref); - break :blk cc_tv.val.toEnum(cc_tv.ty, std.builtin.CallingConvention); + break :blk cc_tv.val.toEnum(std.builtin.CallingConvention); } else .Unspecified; const align_val: Value = if (small.has_align) blk: { @@ -6554,7 +6602,7 @@ fn safetyPanic( }); errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); try new_decl.finalizeNewArena(&new_decl_arena); - break :msg_inst try sema.analyzeDeclRef(block, .unneeded, new_decl); + break :msg_inst try sema.analyzeDeclRef(new_decl); }; const casted_msg_inst = try sema.coerce(block, Type.initTag(.const_slice_u8), msg_inst, src); @@ -6761,11 +6809,16 @@ fn fieldPtr( switch (object_ty.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.ref_val.create( + try Value.Tag.decl_ref.create( arena, - try Value.Tag.int_u64.create(arena, object_ty.arrayLen()), + try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), + ), ), ); } else { @@ -6780,18 +6833,25 @@ fn fieldPtr( .Pointer => { const ptr_child = object_ty.elemType(); if (ptr_child.isSlice()) { + // Here for the ptr and len fields what we need to do is the situation + // when a temporary has its address taken, e.g. `&a[c..d].len`. + // This value may be known at compile-time or runtime. In the former + // case, it should create an anonymous Decl and return a decl_ref to it. + // In the latter case, it should add an `alloc` instruction, store + // the runtime value to it, and then return the `alloc`. + // In both cases the pointer should be const. if (mem.eql(u8, field_name, "ptr")) { return mod.fail( &block.base, field_name_src, - "cannot obtain reference to pointer field of slice '{}'", + "TODO: implement reference to 'ptr' field of slice '{}'", .{object_ty}, ); } else if (mem.eql(u8, field_name, "len")) { return mod.fail( &block.base, field_name_src, - "cannot obtain reference to length field of slice '{}'", + "TODO: implement reference to 'len' field of slice '{}'", .{object_ty}, ); } else { @@ -6805,11 +6865,16 @@ fn fieldPtr( } else switch (ptr_child.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.ref_val.create( + try Value.Tag.decl_ref.create( arena, - try Value.Tag.int_u64.create(arena, ptr_child.arrayLen()), + try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), + ), ), ); } else { @@ -6848,13 +6913,16 @@ fn fieldPtr( }); } else (try mod.getErrorValue(field_name)).key; + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.ref_val.create( + try Value.Tag.decl_ref.create( arena, - try Value.Tag.@"error".create(arena, .{ - .name = name, - }), + try anon_decl.finish( + child_type, + try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), + ), ), ); }, @@ -6901,10 +6969,17 @@ fn fieldPtr( return mod.failWithOwnedErrorMsg(&block.base, msg); }; const field_index_u32 = @intCast(u32, field_index); - const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32); + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.ref_val.create(arena, enum_val), + try Value.Tag.decl_ref.create( + arena, + try anon_decl.finish( + child_type, + try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), + ), + ), ); }, else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), @@ -6951,7 +7026,7 @@ fn namespaceLookupRef( decl_name: []const u8, ) CompileError!?Air.Inst.Ref { const decl = (try sema.namespaceLookup(block, src, namespace, decl_name)) orelse return null; - return try sema.analyzeDeclRef(block, src, decl); + return try sema.analyzeDeclRef(decl); } fn structFieldPtr( @@ -7207,13 +7282,15 @@ fn elemPtrArray( fn coerce( sema: *Sema, block: *Scope.Block, - dest_type: Type, + dest_type_unresolved: Type, inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - if (dest_type.tag() == .var_args_param) { + if (dest_type_unresolved.tag() == .var_args_param) { return sema.coerceVarArgParam(block, inst, inst_src); } + const dest_type_src = inst_src; // TODO better source location + const dest_type = try sema.resolveTypeFields(block, dest_type_src, dest_type_unresolved); const inst_ty = sema.typeOf(inst); // If the types are the same, we can return the operand. @@ -7554,17 +7631,17 @@ fn analyzeDeclVal( if (sema.decl_val_table.get(decl)) |result| { return result; } - const decl_ref = try sema.analyzeDeclRef(block, src, decl); + const decl_ref = try sema.analyzeDeclRef(decl); const result = try sema.analyzeLoad(block, src, decl_ref, src); if (Air.refToIndex(result)) |index| { if (sema.air_instructions.items(.tag)[index] == .constant) { - sema.decl_val_table.put(sema.gpa, decl, result) catch {}; + try sema.decl_val_table.put(sema.gpa, decl, result); } } return result; } -fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl) CompileError!Air.Inst.Ref { +fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { try sema.mod.declareDeclDependency(sema.owner_decl, decl); sema.mod.ensureDeclAnalyzed(decl) catch |err| { if (sema.func) |func| { @@ -7576,8 +7653,10 @@ fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl }; const decl_tv = try decl.typedValue(); - if (decl_tv.val.tag() == .variable) { - return sema.analyzeVarRef(block, src, decl_tv); + if (decl_tv.val.castTag(.variable)) |payload| { + const variable = payload.data; + const ty = try Module.simplePtrType(sema.arena, decl_tv.ty, variable.is_mutable, .One); + return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl)); } return sema.addConstant( try Module.simplePtrType(sema.arena, decl_tv.ty, false, .One), @@ -7585,26 +7664,6 @@ fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl ); } -fn analyzeVarRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, tv: TypedValue) CompileError!Air.Inst.Ref { - const variable = tv.val.castTag(.variable).?.data; - - const ty = try Module.simplePtrType(sema.arena, tv.ty, variable.is_mutable, .One); - if (!variable.is_mutable and !variable.is_extern) { - return sema.addConstant(ty, try Value.Tag.ref_val.create(sema.arena, variable.init)); - } - - const gpa = sema.gpa; - try sema.requireRuntimeBlock(block, src); - try sema.air_variables.append(gpa, variable); - return block.addInst(.{ - .tag = .varptr, - .data = .{ .ty_pl = .{ - .ty = try sema.addType(ty), - .payload = @intCast(u32, sema.air_variables.items.len - 1), - } }, - }); -} - fn analyzeRef( sema: *Sema, block: *Scope.Block, @@ -7615,11 +7674,21 @@ fn analyzeRef( const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| { - return sema.addConstant(ptr_type, try Value.Tag.ref_val.create(sema.arena, val)); + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + return sema.addConstant( + ptr_type, + try Value.Tag.decl_ref.create( + sema.arena, + try anon_decl.finish(operand_ty, try val.copy(anon_decl.arena())), + ), + ); } try sema.requireRuntimeBlock(block, src); - return block.addTyOp(.ref, ptr_type, operand); + const alloc = try block.addTy(.alloc, ptr_type); + try sema.storePtr(block, src, alloc, operand); + return alloc; } fn analyzeLoad( @@ -8447,12 +8516,12 @@ fn getTmpAir(sema: Sema) Air { .instructions = sema.air_instructions.slice(), .extra = sema.air_extra.items, .values = sema.air_values.items, - .variables = sema.air_variables.items, }; } pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref { switch (ty.tag()) { + .u1 => return .u1_type, .u8 => return .u8_type, .i8 => return .i8_type, .u16 => return .u16_type, diff --git a/src/codegen.zig b/src/codegen.zig index 3b822c0f88..b7ba367d54 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -848,13 +848,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .loop => try self.airLoop(inst), .not => try self.airNot(inst), .ptrtoint => try self.airPtrToInt(inst), - .ref => try self.airRef(inst), .ret => try self.airRet(inst), .store => try self.airStore(inst), .struct_field_ptr=> try self.airStructFieldPtr(inst), .struct_field_val=> try self.airStructFieldVal(inst), .switch_br => try self.airSwitch(inst), - .varptr => try self.airVarPtr(inst), .slice_ptr => try self.airSlicePtr(inst), .slice_len => try self.airSliceLen(inst), @@ -1340,13 +1338,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } - fn airVarPtr(self: *Self, inst: Air.Inst.Index) !void { - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { - else => return self.fail("TODO implement varptr for {}", .{self.target.cpu.arch}), - }; - return self.finishAir(inst, result, .{ .none, .none, .none }); - } - fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { @@ -2833,38 +2824,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return bt.finishAir(result); } - fn airRef(self: *Self, inst: Air.Inst.Index) !void { - const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { - const operand_ty = self.air.typeOf(ty_op.operand); - const operand = try self.resolveInst(ty_op.operand); - switch (operand) { - .unreach => unreachable, - .dead => unreachable, - .none => break :result MCValue{ .none = {} }, - - .immediate, - .register, - .ptr_stack_offset, - .ptr_embedded_in_code, - .compare_flags_unsigned, - .compare_flags_signed, - => { - const stack_offset = try self.allocMemPtr(inst); - try self.genSetStack(operand_ty, stack_offset, operand); - break :result MCValue{ .ptr_stack_offset = stack_offset }; - }, - - .stack_offset => |offset| break :result MCValue{ .ptr_stack_offset = offset }, - .embedded_in_code => |offset| break :result MCValue{ .ptr_embedded_in_code = offset }, - .memory => |vaddr| break :result MCValue{ .immediate = vaddr }, - - .undef => return self.fail("TODO implement ref on an undefined value", .{}), - } - }; - return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); - } - fn ret(self: *Self, mcv: MCValue) !void { const ret_ty = self.fn_type.fnReturnType(); try self.setRegOrMem(ret_ty, self.ret_mcv, mcv); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 7299b21a61..a8ec677753 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -283,22 +283,7 @@ pub const DeclGen = struct { }, else => switch (t.ptrSize()) { .Slice => unreachable, - .Many => { - if (val.castTag(.ref_val)) |ref_val_payload| { - const sub_val = ref_val_payload.data; - if (sub_val.castTag(.bytes)) |bytes_payload| { - const bytes = bytes_payload.data; - try writer.writeByte('('); - try dg.renderType(writer, t); - // TODO: make our own C string escape instead of using std.zig.fmtEscapes - try writer.print(")\"{}\"", .{std.zig.fmtEscapes(bytes)}); - } else { - unreachable; - } - } else { - unreachable; - } - }, + .Many => unreachable, .One => { var arena = std.heap.ArenaAllocator.init(dg.module.gpa); defer arena.deinit(); @@ -934,10 +919,8 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .br => try airBr(o, inst), .switch_br => try airSwitchBr(o, inst), .wrap_optional => try airWrapOptional(o, inst), - .ref => try airRef(o, inst), .struct_field_ptr => try airStructFieldPtr(o, inst), .struct_field_val => try airStructFieldVal(o, inst), - .varptr => try airVarPtr(o, inst), .slice_ptr => try airSliceField(o, inst, ".ptr;\n"), .slice_len => try airSliceField(o, inst, ".len;\n"), @@ -996,12 +979,6 @@ fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue return local; } -fn airVarPtr(o: *Object, inst: Air.Inst.Index) !CValue { - const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; - const variable = o.air.variables[ty_pl.payload]; - return CValue{ .decl_ref = variable.owner_decl }; -} - fn airAlloc(o: *Object, inst: Air.Inst.Index) !CValue { const writer = o.writer(); const inst_ty = o.air.typeOfIndex(inst); @@ -1653,22 +1630,6 @@ fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue { return local; } -fn airRef(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) - return CValue.none; - - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); - - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); - try writer.writeAll(" = "); - try o.writeCValue(writer, operand); - try writer.writeAll(";\n"); - return local; -} - fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue { if (o.liveness.isUnused(inst)) return CValue.none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 22f117aa1c..0e9a572bea 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -699,29 +699,12 @@ pub const DeclGen = struct { .decl_ref => { const decl = tv.val.castTag(.decl_ref).?.data; const val = try self.resolveGlobalDecl(decl); - - const usize_type = try self.llvmType(Type.initTag(.usize)); - - // TODO: second index should be the index into the memory! - var indices: [2]*const llvm.Value = .{ - usize_type.constNull(), - usize_type.constNull(), - }; - - return val.constInBoundsGEP(&indices, indices.len); - }, - .ref_val => { - //const elem_value = tv.val.castTag(.ref_val).?.data; - //const elem_type = tv.ty.castPointer().?.data; - //const alloca = fg.?.buildAlloca(try self.llvmType(elem_type)); - //_ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca); - //return alloca; - // TODO eliminate the ref_val Value Tag - return self.todo("implement const of pointer tag ref_val", .{}); + return val.constBitCast(llvm_type); }, .variable => { const variable = tv.val.castTag(.variable).?.data; - return self.resolveGlobalDecl(variable.owner_decl); + const val = try self.resolveGlobalDecl(variable.owner_decl); + return val.constBitCast(llvm_type); }, .slice => { const slice = tv.val.castTag(.slice).?.data; @@ -977,7 +960,6 @@ pub const FuncGen = struct { .ret => try self.airRet(inst), .store => try self.airStore(inst), .assembly => try self.airAssembly(inst), - .varptr => try self.airVarPtr(inst), .slice_ptr => try self.airSliceField(inst, 0), .slice_len => try self.airSliceField(inst, 1), @@ -1001,7 +983,6 @@ pub const FuncGen = struct { .constant => unreachable, .const_ty => unreachable, - .ref => unreachable, // TODO eradicate this instruction .unreach => self.airUnreach(inst), .dbg_stmt => blk: { // TODO: implement debug info @@ -1180,16 +1161,6 @@ pub const FuncGen = struct { return null; } - fn airVarPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; - - const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; - const variable = self.air.variables[ty_pl.payload]; - const decl_llvm_value = self.dg.resolveGlobalDecl(variable.owner_decl); - return decl_llvm_value; - } - fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 6d09d69447..0977d6128d 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -112,6 +112,9 @@ pub const Value = opaque { ConstantIndices: [*]const *const Value, NumIndices: c_uint, ) *const Value; + + pub const constBitCast = LLVMConstBitCast; + extern fn LLVMConstBitCast(ConstantVal: *const Value, ToType: *const Type) *const Value; }; pub const Type = opaque { diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index bf9010fbff..37cc6bc59c 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -754,22 +754,21 @@ pub const Context = struct { } /// Generates the wasm bytecode for the declaration belonging to `Context` - pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result { - switch (typed_value.ty.zigTypeTag()) { + pub fn gen(self: *Context, ty: Type, val: Value) InnerError!Result { + switch (ty.zigTypeTag()) { .Fn => { try self.genFunctype(); - if (typed_value.val.castTag(.extern_fn)) |_| return Result.appended; // don't need code body for extern functions + if (val.tag() == .extern_fn) { + return Result.appended; // don't need code body for extern functions + } return self.fail("TODO implement wasm codegen for function pointers", .{}); }, .Array => { - if (typed_value.val.castTag(.bytes)) |payload| { - if (typed_value.ty.sentinel()) |sentinel| { + if (val.castTag(.bytes)) |payload| { + if (ty.sentinel()) |sentinel| { try self.code.appendSlice(payload.data); - switch (try self.gen(.{ - .ty = typed_value.ty.elemType(), - .val = sentinel, - })) { + switch (try self.gen(ty.elemType(), sentinel)) { .appended => return Result.appended, .externally_managed => |data| { try self.code.appendSlice(data); @@ -781,13 +780,17 @@ pub const Context = struct { } else return self.fail("TODO implement gen for more kinds of arrays", .{}); }, .Int => { - const info = typed_value.ty.intInfo(self.target); + const info = ty.intInfo(self.target); if (info.bits == 8 and info.signedness == .unsigned) { - const int_byte = typed_value.val.toUnsignedInt(); + const int_byte = val.toUnsignedInt(); try self.code.append(@intCast(u8, int_byte)); return Result.appended; } - return self.fail("TODO: Implement codegen for int type: '{}'", .{typed_value.ty}); + return self.fail("TODO: Implement codegen for int type: '{}'", .{ty}); + }, + .Enum => { + try self.emitConstant(val, ty); + return Result.appended; }, else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}), } @@ -969,7 +972,7 @@ pub const Context = struct { return WValue{ .code_offset = offset }; } - fn emitConstant(self: *Context, value: Value, ty: Type) InnerError!void { + fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void { const writer = self.code.writer(); switch (ty.zigTypeTag()) { .Int => { @@ -982,10 +985,10 @@ pub const Context = struct { const int_info = ty.intInfo(self.target); // write constant switch (int_info.signedness) { - .signed => try leb.writeILEB128(writer, value.toSignedInt()), + .signed => try leb.writeILEB128(writer, val.toSignedInt()), .unsigned => switch (int_info.bits) { - 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, value.toUnsignedInt()))), - 33...64 => try leb.writeILEB128(writer, @bitCast(i64, value.toUnsignedInt())), + 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, val.toUnsignedInt()))), + 33...64 => try leb.writeILEB128(writer, @bitCast(i64, val.toUnsignedInt())), else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}), }, } @@ -994,7 +997,7 @@ pub const Context = struct { // write opcode try writer.writeByte(wasm.opcode(.i32_const)); // write constant - try leb.writeILEB128(writer, value.toSignedInt()); + try leb.writeILEB128(writer, val.toSignedInt()); }, .Float => { // write opcode @@ -1005,13 +1008,13 @@ pub const Context = struct { try writer.writeByte(wasm.opcode(opcode)); // write constant switch (ty.floatBits(self.target)) { - 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, value.toFloat(f32))), - 64 => try writer.writeIntLittle(u64, @bitCast(u64, value.toFloat(f64))), + 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, val.toFloat(f32))), + 64 => try writer.writeIntLittle(u64, @bitCast(u64, val.toFloat(f64))), else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}), } }, .Pointer => { - if (value.castTag(.decl_ref)) |payload| { + if (val.castTag(.decl_ref)) |payload| { const decl = payload.data; // offset into the offset table within the 'data' section @@ -1024,11 +1027,11 @@ pub const Context = struct { try writer.writeByte(wasm.opcode(.i32_load)); try leb.writeULEB128(writer, @as(u32, 0)); try leb.writeULEB128(writer, @as(u32, 0)); - } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{value.tag()}); + } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()}); }, .Void => {}, .Enum => { - if (value.castTag(.enum_field_index)) |field_index| { + if (val.castTag(.enum_field_index)) |field_index| { switch (ty.tag()) { .enum_simple => { try writer.writeByte(wasm.opcode(.i32_const)); @@ -1049,20 +1052,20 @@ pub const Context = struct { } else { var int_tag_buffer: Type.Payload.Bits = undefined; const int_tag_ty = ty.intTagType(&int_tag_buffer); - try self.emitConstant(value, int_tag_ty); + try self.emitConstant(val, int_tag_ty); } }, .ErrorSet => { - const error_index = self.global_error_set.get(value.getError().?).?; + const error_index = self.global_error_set.get(val.getError().?).?; try writer.writeByte(wasm.opcode(.i32_const)); try leb.writeULEB128(writer, error_index); }, .ErrorUnion => { - const data = value.castTag(.error_union).?.data; + const data = val.castTag(.error_union).?.data; const error_type = ty.errorUnionSet(); const payload_type = ty.errorUnionPayload(); - if (value.getError()) |_| { - // write the error value + if (val.getError()) |_| { + // write the error val try self.emitConstant(data, error_type); // no payload, so write a '0' const @@ -1085,7 +1088,7 @@ pub const Context = struct { } /// Returns a `Value` as a signed 32 bit value. - /// It's illegale to provide a value with a type that cannot be represented + /// It's illegal to provide a value with a type that cannot be represented /// as an integer value. fn valueAsI32(self: Context, val: Value, ty: Type) i32 { switch (ty.zigTypeTag()) { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 23d0543494..3c3cd4eef3 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -275,7 +275,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { defer context.deinit(); // generate the 'code' section for the function declaration - const result = context.gen(.{ .ty = decl.ty, .val = decl.val }) catch |err| switch (err) { + const result = context.gen(decl.ty, decl.val) catch |err| switch (err) { error.CodegenFail => { decl.analysis = .codegen_failure; try module.failed_decls.put(module.gpa, decl, context.err_msg); diff --git a/src/print_air.zig b/src/print_air.zig index 2d5d6b588e..5d77c303bb 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -15,12 +15,11 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void { (@sizeOf(Air.Inst.Tag) + 8); const extra_bytes = air.extra.len * @sizeOf(u32); const values_bytes = air.values.len * @sizeOf(Value); - const variables_bytes = air.variables.len * @sizeOf(*Module.Var); const tomb_bytes = liveness.tomb_bits.len * @sizeOf(usize); const liveness_extra_bytes = liveness.extra.len * @sizeOf(u32); const liveness_special_bytes = liveness.special.count() * 8; const total_bytes = @sizeOf(Air) + instruction_bytes + extra_bytes + - values_bytes * variables_bytes + @sizeOf(Liveness) + liveness_extra_bytes + + values_bytes + @sizeOf(Liveness) + liveness_extra_bytes + liveness_special_bytes + tomb_bytes; // zig fmt: off @@ -29,7 +28,6 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void { \\# AIR Instructions: {d} ({}) \\# AIR Extra Data: {d} ({}) \\# AIR Values Bytes: {d} ({}) - \\# AIR Variables Bytes: {d} ({}) \\# Liveness tomb_bits: {} \\# Liveness Extra Data: {d} ({}) \\# Liveness special table: {d} ({}) @@ -39,7 +37,6 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void { air.instructions.len, fmtIntSizeBin(instruction_bytes), air.extra.len, fmtIntSizeBin(extra_bytes), air.values.len, fmtIntSizeBin(values_bytes), - air.variables.len, fmtIntSizeBin(variables_bytes), fmtIntSizeBin(tomb_bytes), liveness.extra.len, fmtIntSizeBin(liveness_extra_bytes), liveness.special.count(), fmtIntSizeBin(liveness_special_bytes), @@ -152,7 +149,6 @@ const Writer = struct { .not, .bitcast, .load, - .ref, .floatcast, .intcast, .optional_payload, @@ -174,7 +170,6 @@ const Writer = struct { .struct_field_ptr => try w.writeStructField(s, inst), .struct_field_val => try w.writeStructField(s, inst), - .varptr => try w.writeVarPtr(s, inst), .constant => try w.writeConstant(s, inst), .assembly => try w.writeAssembly(s, inst), .dbg_stmt => try w.writeDbgStmt(s, inst), @@ -243,12 +238,6 @@ const Writer = struct { try s.print(", {d}", .{extra.data.field_index}); } - fn writeVarPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { - _ = w; - _ = inst; - try s.writeAll("TODO"); - } - fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const val = w.air.values[ty_pl.payload]; diff --git a/src/value.zig b/src/value.zig index 5d9fd27414..d3317ef31d 100644 --- a/src/value.zig +++ b/src/value.zig @@ -100,8 +100,6 @@ pub const Value = extern union { function, extern_fn, variable, - /// Represents a pointer to another immutable value. - ref_val, /// Represents a comptime variables storage. comptime_alloc, /// Represents a pointer to a decl, not the value of the decl. @@ -126,6 +124,8 @@ pub const Value = extern union { enum_field_index, @"error", error_union, + /// A pointer to the payload of an error union, based on a pointer to an error union. + eu_payload_ptr, /// An instance of a struct. @"struct", /// An instance of a union. @@ -214,9 +214,9 @@ pub const Value = extern union { .decl_ref, => Payload.Decl, - .ref_val, .repeated, .error_union, + .eu_payload_ptr, => Payload.SubValue, .bytes, @@ -407,15 +407,6 @@ pub const Value = extern union { .function => return self.copyPayloadShallow(allocator, Payload.Function), .extern_fn => return self.copyPayloadShallow(allocator, Payload.Decl), .variable => return self.copyPayloadShallow(allocator, Payload.Variable), - .ref_val => { - const payload = self.castTag(.ref_val).?; - const new_payload = try allocator.create(Payload.SubValue); - new_payload.* = .{ - .base = payload.base, - .data = try payload.data.copy(allocator), - }; - return Value{ .ptr_otherwise = &new_payload.base }; - }, .comptime_alloc => return self.copyPayloadShallow(allocator, Payload.ComptimeAlloc), .decl_ref => return self.copyPayloadShallow(allocator, Payload.Decl), .elem_ptr => { @@ -443,8 +434,8 @@ pub const Value = extern union { return Value{ .ptr_otherwise = &new_payload.base }; }, .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), - .repeated => { - const payload = self.castTag(.repeated).?; + .repeated, .error_union, .eu_payload_ptr => { + const payload = self.cast(Payload.SubValue).?; const new_payload = try allocator.create(Payload.SubValue); new_payload.* = .{ .base = payload.base, @@ -489,15 +480,6 @@ pub const Value = extern union { }, .enum_field_index => return self.copyPayloadShallow(allocator, Payload.U32), .@"error" => return self.copyPayloadShallow(allocator, Payload.Error), - .error_union => { - const payload = self.castTag(.error_union).?; - const new_payload = try allocator.create(Payload.SubValue); - new_payload.* = .{ - .base = payload.base, - .data = try payload.data.copy(allocator), - }; - return Value{ .ptr_otherwise = &new_payload.base }; - }, .@"struct" => @panic("TODO can't copy struct value without knowing the type"), .@"union" => @panic("TODO can't copy union value without knowing the type"), @@ -609,11 +591,6 @@ pub const Value = extern union { .function => return out_stream.print("(function '{s}')", .{val.castTag(.function).?.data.owner_decl.name}), .extern_fn => return out_stream.writeAll("(extern function)"), .variable => return out_stream.writeAll("(variable)"), - .ref_val => { - const ref_val = val.castTag(.ref_val).?.data; - try out_stream.writeAll("&const "); - val = ref_val; - }, .comptime_alloc => { const ref_val = val.castTag(.comptime_alloc).?.data.val; try out_stream.writeAll("&"); @@ -648,6 +625,10 @@ pub const Value = extern union { // TODO to print this it should be error{ Set, Items }!T(val), but we need the type for that .error_union => return out_stream.print("error_union_val({})", .{val.castTag(.error_union).?.data}), .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"), + .eu_payload_ptr => { + try out_stream.writeAll("(eu_payload_ptr)"); + val = val.castTag(.eu_payload_ptr).?.data; + }, }; } @@ -758,7 +739,6 @@ pub const Value = extern union { .function, .extern_fn, .variable, - .ref_val, .comptime_alloc, .decl_ref, .elem_ptr, @@ -780,18 +760,21 @@ pub const Value = extern union { .@"union", .inferred_alloc, .abi_align_default, + .eu_payload_ptr, => unreachable, }; } /// Asserts the type is an enum type. - pub fn toEnum(val: Value, enum_ty: Type, comptime E: type) E { - _ = enum_ty; - // TODO this needs to resolve other kinds of Value tags rather than - // assuming the tag will be .enum_field_index. - const field_index = val.castTag(.enum_field_index).?.data; - // TODO should `@intToEnum` do this `@intCast` for you? - return @intToEnum(E, @intCast(@typeInfo(E).Enum.tag_type, field_index)); + pub fn toEnum(val: Value, comptime E: type) E { + switch (val.tag()) { + .enum_field_index => { + const field_index = val.castTag(.enum_field_index).?.data; + // TODO should `@intToEnum` do this `@intCast` for you? + return @intToEnum(E, @intCast(@typeInfo(E).Enum.tag_type, field_index)); + }, + else => unreachable, + } } /// Asserts the value is an integer. @@ -1255,6 +1238,9 @@ pub const Value = extern union { .slice => { @panic("TODO Value.hash for slice"); }, + .eu_payload_ptr => { + @panic("TODO Value.hash for eu_payload_ptr"); + }, .int_u64 => { const payload = self.castTag(.int_u64).?; std.hash.autoHash(&hasher, payload.data); @@ -1263,10 +1249,6 @@ pub const Value = extern union { const payload = self.castTag(.int_i64).?; std.hash.autoHash(&hasher, payload.data); }, - .ref_val => { - const payload = self.castTag(.ref_val).?; - std.hash.autoHash(&hasher, payload.data.hash()); - }, .comptime_alloc => { const payload = self.castTag(.comptime_alloc).?; std.hash.autoHash(&hasher, payload.data.val.hash()); @@ -1367,7 +1349,6 @@ pub const Value = extern union { pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { return switch (self.tag()) { .comptime_alloc => self.castTag(.comptime_alloc).?.data.val, - .ref_val => self.castTag(.ref_val).?.data, .decl_ref => self.castTag(.decl_ref).?.data.value(), .elem_ptr => { const elem_ptr = self.castTag(.elem_ptr).?.data; @@ -1379,6 +1360,11 @@ pub const Value = extern union { const container_val = try field_ptr.container_ptr.pointerDeref(allocator); return container_val.fieldValue(allocator, field_ptr.field_index); }, + .eu_payload_ptr => { + const err_union_ptr = self.castTag(.eu_payload_ptr).?.data; + const err_union_val = try err_union_ptr.pointerDeref(allocator); + return err_union_val.castTag(.error_union).?.data; + }, else => unreachable, }; @@ -1390,7 +1376,6 @@ pub const Value = extern union { .bytes => val.castTag(.bytes).?.data.len, .array => val.castTag(.array).?.data.len, .slice => val.castTag(.slice).?.data.len.toUnsignedInt(), - .ref_val => sliceLen(val.castTag(.ref_val).?.data), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; if (decl.ty.zigTypeTag() == .Array) { @@ -1576,7 +1561,6 @@ pub const Value = extern union { .int_i64, .int_big_positive, .int_big_negative, - .ref_val, .comptime_alloc, .decl_ref, .elem_ptr, @@ -1599,6 +1583,7 @@ pub const Value = extern union { .@"union", .null_value, .abi_align_default, + .eu_payload_ptr, => false, .undef => unreachable, diff --git a/test/cases.zig b/test/cases.zig index f235992f71..840ee7a4ac 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1182,10 +1182,11 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.obj("extern variable has no type", linux_x64); case.addError( \\comptime { - \\ _ = foo; + \\ const x = foo + foo; + \\ _ = x; \\} \\extern var foo: i32; - , &[_][]const u8{":2:9: error: unable to resolve comptime value"}); + , &[_][]const u8{":2:15: error: unable to resolve comptime value"}); case.addError( \\export fn entry() void { \\ _ = foo; -- cgit v1.2.3 From 040c6eaaa03bbcfcdeadbe835c1c2f209e9f401e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jul 2021 19:30:37 -0700 Subject: stage2: garbage collect unused anon decls After this change, the frontend and backend cooperate to keep track of which Decls are actually emitted into the machine code. When any backend sees a `decl_ref` Value, it must mark the corresponding Decl `alive` field to true. This prevents unused comptime data from spilling into the output object files. For example, if you do an `inline for` loop, previously, any intermediate value calculations would have gone into the object file. Now they are garbage collected immediately after the owner Decl has its machine code generated. In the frontend, when it is time to send a Decl to the linker, if it has not been marked "alive" then it is deleted instead. Additional improvements: * Resolve type ABI layouts after successful semantic analysis of a Decl. This is needed so that the backend has access to struct fields. * Sema: fix incorrect logic in resolveMaybeUndefVal. It should return "not comptime known" instead of a compile error for global variables. * `Value.pointerDeref` now returns `null` in the case that the pointer deref cannot happen at compile-time. This is true for global variables, for example. Another example is if a comptime known pointer has a hard coded address value. * Binary arithmetic sets the requireRuntimeBlock source location to the lhs_src or rhs_src as appropriate instead of on the operator node. * Fix LLVM codegen for slice_elem_val which had the wrong logic for when the operand was not a pointer. As noted in the comment in the implementation of deleteUnusedDecl, a future improvement will be to rework the frontend/linker interface to remove the frontend's responsibility of calling allocateDeclIndexes. I discovered some issues with the plan9 linker backend that are related to this, and worked around them for now. --- src/Compilation.zig | 10 +++- src/Module.zig | 60 +++++++++++++++++--- src/Sema.zig | 154 ++++++++++++++++++++++++++------------------------- src/codegen.zig | 7 +-- src/codegen/c.zig | 24 +++----- src/codegen/llvm.zig | 51 +++++++++++------ src/codegen/wasm.zig | 1 + src/link/Plan9.zig | 19 +++++-- src/value.zig | 45 +++++++++++---- test/stage2/cbe.zig | 2 +- 10 files changed, 234 insertions(+), 139 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 8672a346c3..f8f8cea328 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2061,11 +2061,19 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .complete, .codegen_failure_retryable => { if (build_options.omit_stage2) @panic("sadly stage2 is omitted from this build to save memory on the CI server"); + const module = self.bin_file.options.module.?; assert(decl.has_tv); assert(decl.ty.hasCodeGenBits()); - try module.linkerUpdateDecl(decl); + if (decl.alive) { + try module.linkerUpdateDecl(decl); + continue; + } + + // Instead of sending this decl to the linker, we actually will delete it + // because we found out that it in fact was never referenced. + module.deleteUnusedDecl(decl); }, }, .codegen_func => |func| switch (func.owner_decl.analysis) { diff --git a/src/Module.zig b/src/Module.zig index f89f72bc65..909e54ffa2 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -255,6 +255,15 @@ pub const Decl = struct { has_align: bool, /// Whether the ZIR code provides a linksection instruction. has_linksection: bool, + /// Flag used by garbage collection to mark and sweep. + /// Decls which correspond to an AST node always have this field set to `true`. + /// Anonymous Decls are initialized with this field set to `false` and then it + /// is the responsibility of machine code backends to mark it `true` whenever + /// a `decl_ref` Value is encountered that points to this Decl. + /// When the `codegen_decl` job is encountered in the main work queue, if the + /// Decl is marked alive, then it sends the Decl to the linker. Otherwise it + /// deletes the Decl on the spot. + alive: bool, /// Represents the position of the code in the output file. /// This is populated regardless of semantic analysis and code generation. @@ -2869,6 +2878,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { new_decl.val = struct_val; new_decl.has_tv = true; new_decl.owns_tv = true; + new_decl.alive = true; // This Decl corresponds to a File and is therefore always alive. new_decl.analysis = .in_progress; new_decl.generation = mod.generation; @@ -2990,6 +3000,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { if (linksection_ref == .none) break :blk Value.initTag(.null_value); break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val; }; + try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty); // We need the memory for the Type to go into the arena for the Decl var decl_arena = std.heap.ArenaAllocator.init(gpa); @@ -3027,8 +3038,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { const is_inline = decl_tv.ty.fnCallingConvention() == .Inline; if (!is_inline and decl_tv.ty.hasCodeGenBits()) { // We don't fully codegen the decl until later, but we do need to reserve a global - // offset table index for it. This allows us to codegen decls out of dependency order, - // increasing how many computations can be done in parallel. + // offset table index for it. This allows us to codegen decls out of dependency + // order, increasing how many computations can be done in parallel. try mod.comp.bin_file.allocateDeclIndexes(decl); try mod.comp.work_queue.writeItem(.{ .codegen_func = func }); if (type_changed and mod.emit_h != null) { @@ -3387,6 +3398,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi new_decl.has_align = has_align; new_decl.has_linksection = has_linksection; new_decl.zir_decl_index = @intCast(u32, decl_sub_index); + new_decl.alive = true; // This Decl corresponds to an AST node and therefore always alive. return; } gpa.free(decl_name); @@ -3526,6 +3538,43 @@ pub fn clearDecl( decl.analysis = .unreferenced; } +pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void { + log.debug("deleteUnusedDecl {*} ({s})", .{ decl, decl.name }); + + // TODO: remove `allocateDeclIndexes` and make the API that the linker backends + // are required to notice the first time `updateDecl` happens and keep track + // of it themselves. However they can rely on getting a `freeDecl` call if any + // `updateDecl` or `updateFunc` calls happen. This will allow us to avoid any call + // into the linker backend here, since the linker backend will never have been told + // about the Decl in the first place. + // Until then, we did call `allocateDeclIndexes` on this anonymous Decl and so we + // must call `freeDecl` in the linker backend now. + if (decl.has_tv) { + if (decl.ty.hasCodeGenBits()) { + mod.comp.bin_file.freeDecl(decl); + } + } + + const dependants = decl.dependants.keys(); + assert(dependants[0].namespace.anon_decls.swapRemove(decl)); + + for (dependants) |dep| { + dep.removeDependency(decl); + } + + for (decl.dependencies.keys()) |dep| { + dep.removeDependant(decl); + } + decl.destroy(mod); +} + +pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void { + log.debug("deleteAnonDecl {*} ({s})", .{ decl, decl.name }); + const scope_decl = scope.ownerDecl().?; + assert(scope_decl.namespace.anon_decls.swapRemove(decl)); + decl.destroy(mod); +} + /// Delete all the Export objects that are caused by this Decl. Re-analysis of /// this Decl will cause them to be re-created (or not). fn deleteDeclExports(mod: *Module, decl: *Decl) void { @@ -3713,6 +3762,7 @@ fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node .is_exported = false, .has_linksection = false, .has_align = false, + .alive = false, }; return new_decl; } @@ -3802,12 +3852,6 @@ pub fn analyzeExport( errdefer de_gop.value_ptr.* = mod.gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1); } -pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void { - const scope_decl = scope.ownerDecl().?; - assert(scope_decl.namespace.anon_decls.swapRemove(decl)); - decl.destroy(mod); -} - /// Takes ownership of `name` even if it returns an error. pub fn createAnonymousDeclNamed( mod: *Module, diff --git a/src/Sema.zig b/src/Sema.zig index 95baae7e92..0da38d9a76 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -696,7 +696,7 @@ fn resolveMaybeUndefVal( ) CompileError!?Value { const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; if (val.tag() == .variable) { - return sema.failWithNeededComptime(block, src); + return null; } return val; } @@ -2917,12 +2917,13 @@ fn zirOptionalPayloadPtr( const child_pointer = try Module.simplePtrType(sema.arena, child_type, !optional_ptr_ty.isConstPtr(), .One); if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| { - const val = try pointer_val.pointerDeref(sema.arena); - if (val.isNull()) { - return sema.mod.fail(&block.base, src, "unable to unwrap null", .{}); + if (try pointer_val.pointerDeref(sema.arena)) |val| { + if (val.isNull()) { + return sema.mod.fail(&block.base, src, "unable to unwrap null", .{}); + } + // The same Value represents the pointer to the optional and the payload. + return sema.addConstant(child_pointer, pointer_val); } - // The same Value represents the pointer to the optional and the payload. - return sema.addConstant(child_pointer, pointer_val); } try sema.requireRuntimeBlock(block, src); @@ -3027,14 +3028,15 @@ fn zirErrUnionPayloadPtr( const operand_pointer_ty = try Module.simplePtrType(sema.arena, payload_ty, !operand_ty.isConstPtr(), .One); if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { - const val = try pointer_val.pointerDeref(sema.arena); - if (val.getError()) |name| { - return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); + if (try pointer_val.pointerDeref(sema.arena)) |val| { + if (val.getError()) |name| { + return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); + } + return sema.addConstant( + operand_pointer_ty, + try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), + ); } - return sema.addConstant( - operand_pointer_ty, - try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), - ); } try sema.requireRuntimeBlock(block, src); @@ -3086,10 +3088,11 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co const result_ty = operand_ty.elemType().errorUnionSet(); if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { - const val = try pointer_val.pointerDeref(sema.arena); - assert(val.getError() != null); - const data = val.castTag(.error_union).?.data; - return sema.addConstant(result_ty, data); + if (try pointer_val.pointerDeref(sema.arena)) |val| { + assert(val.getError() != null); + const data = val.castTag(.error_union).?.data; + return sema.addConstant(result_ty, data); + } } try sema.requireRuntimeBlock(block, src); @@ -4920,10 +4923,13 @@ fn analyzeArithmetic( log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value }); return sema.addConstant(scalar_type, value); + } else { + try sema.requireRuntimeBlock(block, rhs_src); } + } else { + try sema.requireRuntimeBlock(block, lhs_src); } - try sema.requireRuntimeBlock(block, src); const air_tag: Air.Inst.Tag = switch (zir_tag) { .add => .add, .addwrap => .addwrap, @@ -6811,16 +6817,10 @@ fn fieldPtr( if (mem.eql(u8, field_name, "len")) { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), + )); } else { return mod.fail( &block.base, @@ -6867,16 +6867,10 @@ fn fieldPtr( if (mem.eql(u8, field_name, "len")) { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), + )); } else { return mod.fail( &block.base, @@ -6915,16 +6909,10 @@ fn fieldPtr( var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - child_type, - try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + child_type, + try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), + )); }, .Struct, .Opaque, .Union => { if (child_type.getNamespace()) |namespace| { @@ -6971,16 +6959,10 @@ fn fieldPtr( const field_index_u32 = @intCast(u32, field_index); var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - child_type, - try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + child_type, + try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), + )); }, else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), } @@ -7671,21 +7653,18 @@ fn analyzeRef( operand: Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const operand_ty = sema.typeOf(operand); - const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - ptr_type, - try Value.Tag.decl_ref.create( - sema.arena, - try anon_decl.finish(operand_ty, try val.copy(anon_decl.arena())), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + operand_ty, + try val.copy(anon_decl.arena()), + )); } try sema.requireRuntimeBlock(block, src); + const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); const alloc = try block.addTy(.alloc, ptr_type); try sema.storePtr(block, src, alloc, operand); return alloc; @@ -7703,11 +7682,10 @@ fn analyzeLoad( .Pointer => ptr_ty.elemType(), else => return sema.mod.fail(&block.base, ptr_src, "expected pointer, found '{}'", .{ptr_ty}), }; - if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| blk: { - if (ptr_val.tag() == .int_u64) - break :blk; // do it at runtime - - return sema.addConstant(elem_ty, try ptr_val.pointerDeref(sema.arena)); + if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { + if (try ptr_val.pointerDeref(sema.arena)) |elem_val| { + return sema.addConstant(elem_ty, elem_val); + } } try sema.requireRuntimeBlock(block, src); @@ -8215,6 +8193,36 @@ fn resolvePeerTypes( return sema.typeOf(chosen); } +pub fn resolveTypeLayout( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + ty: Type, +) CompileError!void { + switch (ty.zigTypeTag()) { + .Pointer => { + return sema.resolveTypeLayout(block, src, ty.elemType()); + }, + .Struct => { + const resolved_ty = try sema.resolveTypeFields(block, src, ty); + const struct_obj = resolved_ty.castTag(.@"struct").?.data; + switch (struct_obj.status) { + .none, .have_field_types => {}, + .field_types_wip, .layout_wip => { + return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty}); + }, + .have_layout => return, + } + struct_obj.status = .layout_wip; + for (struct_obj.fields.values()) |field| { + try sema.resolveTypeLayout(block, src, field.ty); + } + struct_obj.status = .have_layout; + }, + else => {}, + } +} + fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type { switch (ty.tag()) { .@"struct" => { @@ -8222,9 +8230,7 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type switch (struct_obj.status) { .none => {}, .field_types_wip => { - return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ - ty, - }); + return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty}); }, .have_field_types, .have_layout, .layout_wip => return ty, } diff --git a/src/codegen.zig b/src/codegen.zig index b7ba367d54..d16a87adca 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -184,6 +184,7 @@ pub fn generateSymbol( if (typed_value.val.castTag(.decl_ref)) |payload| { const decl = payload.data; if (decl.analysis != .complete) return error.AnalysisFail; + decl.alive = true; // TODO handle the dependency of this symbol on the decl's vaddr. // If the decl changes vaddr, then this symbol needs to get regenerated. const vaddr = bin_file.getDeclVAddr(decl); @@ -4680,13 +4681,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, else => { if (typed_value.val.castTag(.decl_ref)) |payload| { + const decl = payload.data; + decl.alive = true; if (self.bin_file.cast(link.File.Elf)) |elf_file| { - const decl = payload.data; const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { - const decl = payload.data; const got_addr = blk: { const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment; const got = seg.sections.items[macho_file.got_section_index.?]; @@ -4698,11 +4699,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const decl = payload.data; const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { - const decl = payload.data; const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; return MCValue{ .memory = got_addr }; } else { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a8ec677753..826b73317c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -262,6 +262,7 @@ pub const DeclGen = struct { .one => try writer.writeAll("1"), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; + decl.alive = true; // Determine if we must pointer cast. assert(decl.has_tv); @@ -281,21 +282,7 @@ pub const DeclGen = struct { const decl = val.castTag(.extern_fn).?.data; try writer.print("{s}", .{decl.name}); }, - else => switch (t.ptrSize()) { - .Slice => unreachable, - .Many => unreachable, - .One => { - var arena = std.heap.ArenaAllocator.init(dg.module.gpa); - defer arena.deinit(); - - const elem_ty = t.elemType(); - const elem_val = try val.pointerDeref(&arena.allocator); - - try writer.writeAll("&"); - try dg.renderValue(writer, elem_ty, elem_val); - }, - .C => unreachable, - }, + else => unreachable, }, }, .Array => { @@ -421,6 +408,7 @@ pub const DeclGen = struct { .one => try writer.writeAll("1"), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; + decl.alive = true; // Determine if we must pointer cast. assert(decl.has_tv); @@ -433,11 +421,13 @@ pub const DeclGen = struct { } }, .function => { - const func = val.castTag(.function).?.data; - try writer.print("{s}", .{func.owner_decl.name}); + const decl = val.castTag(.function).?.data.owner_decl; + decl.alive = true; + try writer.print("{s}", .{decl.name}); }, .extern_fn => { const decl = val.castTag(.extern_fn).?.data; + decl.alive = true; try writer.print("{s}", .{decl.name}); }, else => unreachable, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0e9a572bea..961ed7ee99 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -673,17 +673,21 @@ pub const DeclGen = struct { } fn genTypedValue(self: *DeclGen, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - const llvm_type = try self.llvmType(tv.ty); - - if (tv.val.isUndef()) + if (tv.val.isUndef()) { + const llvm_type = try self.llvmType(tv.ty); return llvm_type.getUndef(); + } switch (tv.ty.zigTypeTag()) { - .Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(), + .Bool => { + const llvm_type = try self.llvmType(tv.ty); + return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(); + }, .Int => { var bigint_space: Value.BigIntSpace = undefined; const bigint = tv.val.toBigInt(&bigint_space); + const llvm_type = try self.llvmType(tv.ty); if (bigint.eqZero()) return llvm_type.constNull(); if (bigint.limbs.len != 1) { @@ -698,12 +702,17 @@ pub const DeclGen = struct { .Pointer => switch (tv.val.tag()) { .decl_ref => { const decl = tv.val.castTag(.decl_ref).?.data; + decl.alive = true; const val = try self.resolveGlobalDecl(decl); + const llvm_type = try self.llvmType(tv.ty); return val.constBitCast(llvm_type); }, .variable => { - const variable = tv.val.castTag(.variable).?.data; - const val = try self.resolveGlobalDecl(variable.owner_decl); + const decl = tv.val.castTag(.variable).?.data.owner_decl; + decl.alive = true; + const val = try self.resolveGlobalDecl(decl); + const llvm_var_type = try self.llvmType(tv.ty); + const llvm_type = llvm_var_type.pointerType(0); return val.constBitCast(llvm_type); }, .slice => { @@ -783,6 +792,7 @@ pub const DeclGen = struct { .decl_ref => tv.val.castTag(.decl_ref).?.data, else => unreachable, }; + fn_decl.alive = true; return self.resolveLlvmFunction(fn_decl); }, .ErrorSet => { @@ -903,9 +913,7 @@ pub const FuncGen = struct { return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }); } const inst_index = Air.refToIndex(inst).?; - if (self.func_inst_table.get(inst_index)) |value| return value; - - return self.todo("implement global llvm values (or the value is not in the func_inst_table table)", .{}); + return self.func_inst_table.get(inst_index).?; } fn genBody(self: *FuncGen, body: []const Air.Inst.Index) error{ OutOfMemory, CodegenFail }!void { @@ -966,8 +974,8 @@ pub const FuncGen = struct { .struct_field_ptr => try self.airStructFieldPtr(inst), .struct_field_val => try self.airStructFieldVal(inst), - .slice_elem_val => try self.airSliceElemVal(inst, false), - .ptr_slice_elem_val => try self.airSliceElemVal(inst, true), + .slice_elem_val => try self.airSliceElemVal(inst), + .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), .optional_payload => try self.airOptionalPayload(inst, false), .optional_payload_ptr => try self.airOptionalPayload(inst, true), @@ -1170,11 +1178,20 @@ pub const FuncGen = struct { return self.builder.buildExtractValue(operand, index, ""); } - fn airSliceElemVal( - self: *FuncGen, - inst: Air.Inst.Index, - operand_is_ptr: bool, - ) !?*const llvm.Value { + fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const base_ptr = self.builder.buildExtractValue(lhs, 0, ""); + const indices: [1]*const llvm.Value = .{rhs}; + const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + return self.builder.buildLoad(ptr, ""); + } + + fn airPtrSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1182,7 +1199,7 @@ pub const FuncGen = struct { const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); - const base_ptr = if (!operand_is_ptr) lhs else ptr: { + const base_ptr = ptr: { const index_type = self.context.intType(32); const indices: [2]*const llvm.Value = .{ index_type.constNull(), diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 37cc6bc59c..2f1632e0fc 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -1016,6 +1016,7 @@ pub const Context = struct { .Pointer => { if (val.castTag(.decl_ref)) |payload| { const decl = payload.data; + decl.alive = true; // offset into the offset table within the 'data' section const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8; diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 135b59f82b..3b2aae85bc 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -224,7 +224,9 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void { const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; - assert(self.got_len == self.fn_decl_table.count() + self.data_decl_table.count()); + // TODO I changed this assert from == to >= but this code all needs to be audited; see + // the comment in `freeDecl`. + assert(self.got_len >= self.fn_decl_table.count() + self.data_decl_table.count()); const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8; var got_table = try self.base.allocator.alloc(u8, got_size); defer self.base.allocator.free(got_table); @@ -358,11 +360,18 @@ fn addDeclExports( } pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { + // TODO this is not the correct check for being function body, + // it could just be a function pointer. + // TODO audit the lifetimes of decls table entries. It's possible to get + // allocateDeclIndexes and then freeDecl without any updateDecl in between. + // However that is planned to change, see the TODO comment in Module.zig + // in the deleteUnusedDecl function. const is_fn = (decl.ty.zigTypeTag() == .Fn); - if (is_fn) - assert(self.fn_decl_table.swapRemove(decl)) - else - assert(self.data_decl_table.swapRemove(decl)); + if (is_fn) { + _ = self.fn_decl_table.swapRemove(decl); + } else { + _ = self.data_decl_table.swapRemove(decl); + } } pub fn updateDeclExports( diff --git a/src/value.zig b/src/value.zig index d3317ef31d..32be34ee2c 100644 --- a/src/value.zig +++ b/src/value.zig @@ -103,6 +103,7 @@ pub const Value = extern union { /// Represents a comptime variables storage. comptime_alloc, /// Represents a pointer to a decl, not the value of the decl. + /// When machine codegen backend sees this, it must set the Decl's `alive` field to true. decl_ref, elem_ptr, field_ptr, @@ -1346,28 +1347,48 @@ pub const Value = extern union { /// Asserts the value is a pointer and dereferences it. /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis. - pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { - return switch (self.tag()) { + pub fn pointerDeref( + self: Value, + allocator: *Allocator, + ) error{ AnalysisFail, OutOfMemory }!?Value { + const sub_val: Value = switch (self.tag()) { .comptime_alloc => self.castTag(.comptime_alloc).?.data.val, - .decl_ref => self.castTag(.decl_ref).?.data.value(), - .elem_ptr => { + .decl_ref => try self.castTag(.decl_ref).?.data.value(), + .elem_ptr => blk: { const elem_ptr = self.castTag(.elem_ptr).?.data; - const array_val = try elem_ptr.array_ptr.pointerDeref(allocator); - return array_val.elemValue(allocator, elem_ptr.index); + const array_val = (try elem_ptr.array_ptr.pointerDeref(allocator)) orelse return null; + break :blk try array_val.elemValue(allocator, elem_ptr.index); }, - .field_ptr => { + .field_ptr => blk: { const field_ptr = self.castTag(.field_ptr).?.data; - const container_val = try field_ptr.container_ptr.pointerDeref(allocator); - return container_val.fieldValue(allocator, field_ptr.field_index); + const container_val = (try field_ptr.container_ptr.pointerDeref(allocator)) orelse return null; + break :blk try container_val.fieldValue(allocator, field_ptr.field_index); }, - .eu_payload_ptr => { + .eu_payload_ptr => blk: { const err_union_ptr = self.castTag(.eu_payload_ptr).?.data; - const err_union_val = try err_union_ptr.pointerDeref(allocator); - return err_union_val.castTag(.error_union).?.data; + const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null; + break :blk err_union_val.castTag(.error_union).?.data; }, + .zero, + .one, + .int_u64, + .int_i64, + .int_big_positive, + .int_big_negative, + .variable, + .extern_fn, + .function, + => return null, + else => unreachable, }; + if (sub_val.tag() == .variable) { + // This would be loading a runtime value at compile-time so we return + // the indicator that this pointer dereference requires being done at runtime. + return null; + } + return sub_val; } pub fn sliceLen(val: Value) u64 { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 6427e2e3b8..4f7d80d1fa 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -49,7 +49,7 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn foo() callconv(y) c_int { \\ return 0; \\} - \\var y: i32 = 1234; + \\var y: @import("std").builtin.CallingConvention = .C; , &.{ ":2:22: error: unable to resolve comptime value", ":5:26: error: unable to resolve comptime value", -- cgit v1.2.3 From 507dc1f2e7fac212e79f152e557cbec98a3c30e9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jul 2021 16:05:46 -0700 Subject: stage2: fix hashing and comparison design flaw with Value * `Value.toType` accepts a buffer parameter instead of an allocator parameter and can no longer fail. * Module: remove the unused `mod: *Module` parameter from various functions. * `Value.compare` now accepts a `Type` parameter which indicates the type of both operands. There is also a `Value.compareHetero` which accepts only Value parameters and supports comparing mixed types. Likewise, `Value.eql` requires a `Type` parameter. * `Value.hash` is removed; instead the hash map context structs now have a `ty: Type` field, and the hash function lives there, where it has access to a Value's Type when it computes a hash. - This allowed the hash function to be greatly simplified and sound in the sense that the same Values, even with different representations, always hash to the same thing. * Sema: Fix source location of zirCmp when an operand is runtime known but needs to be comptime known. * Remove unused target parameter from `Value.floatCast`. --- src/Air.zig | 3 +- src/Module.zig | 11 +- src/RangeSet.zig | 23 ++- src/Sema.zig | 134 +++++++++------- src/type.zig | 43 ++++-- src/value.zig | 457 +++++++++++++------------------------------------------ 6 files changed, 229 insertions(+), 442 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Air.zig b/src/Air.zig index fb95d60d00..d202c079bc 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -503,7 +503,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { pub fn getRefType(air: Air, ref: Air.Inst.Ref) Type { const ref_int = @enumToInt(ref); if (ref_int < Air.Inst.Ref.typed_value_map.len) { - return Air.Inst.Ref.typed_value_map[ref_int].val.toType(undefined) catch unreachable; + var buffer: Value.ToTypeBuffer = undefined; + return Air.Inst.Ref.typed_value_map[ref_int].val.toType(&buffer); } const inst_index = ref_int - Air.Inst.Ref.typed_value_map.len; const air_tags = air.instructions.items(.tag); diff --git a/src/Module.zig b/src/Module.zig index 909e54ffa2..d87f20621c 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4299,7 +4299,6 @@ pub fn simplePtrType( } pub fn ptrType( - mod: *Module, arena: *Allocator, elem_ty: Type, sentinel: ?Value, @@ -4311,7 +4310,6 @@ pub fn ptrType( @"volatile": bool, size: std.builtin.TypeInfo.Pointer.Size, ) Allocator.Error!Type { - _ = mod; assert(host_size == 0 or bit_offset < host_size * 8); // TODO check if type can be represented by simplePtrType @@ -4328,8 +4326,7 @@ pub fn ptrType( }); } -pub fn optionalType(mod: *Module, arena: *Allocator, child_type: Type) Allocator.Error!Type { - _ = mod; +pub fn optionalType(arena: *Allocator, child_type: Type) Allocator.Error!Type { switch (child_type.tag()) { .single_const_pointer => return Type.Tag.optional_single_const_pointer.create( arena, @@ -4344,16 +4341,14 @@ pub fn optionalType(mod: *Module, arena: *Allocator, child_type: Type) Allocator } pub fn arrayType( - mod: *Module, arena: *Allocator, len: u64, sentinel: ?Value, elem_type: Type, ) Allocator.Error!Type { - _ = mod; if (elem_type.eql(Type.initTag(.u8))) { if (sentinel) |some| { - if (some.eql(Value.initTag(.zero))) { + if (some.eql(Value.initTag(.zero), elem_type)) { return Type.Tag.array_u8_sentinel_0.create(arena, len); } } else { @@ -4376,12 +4371,10 @@ pub fn arrayType( } pub fn errorUnionType( - mod: *Module, arena: *Allocator, error_set: Type, payload: Type, ) Allocator.Error!Type { - _ = mod; assert(error_set.zigTypeTag() == .ErrorSet); if (error_set.eql(Type.initTag(.anyerror)) and payload.eql(Type.initTag(.void))) { return Type.initTag(.anyerror_void_error_union); diff --git a/src/RangeSet.zig b/src/RangeSet.zig index fd258d55b0..2a8a55a077 100644 --- a/src/RangeSet.zig +++ b/src/RangeSet.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Order = std.math.Order; +const Type = @import("type.zig").Type; const Value = @import("value.zig").Value; const RangeSet = @This(); const SwitchProngSrc = @import("Module.zig").SwitchProngSrc; @@ -22,9 +23,15 @@ pub fn deinit(self: *RangeSet) void { self.ranges.deinit(); } -pub fn add(self: *RangeSet, first: Value, last: Value, src: SwitchProngSrc) !?SwitchProngSrc { +pub fn add( + self: *RangeSet, + first: Value, + last: Value, + ty: Type, + src: SwitchProngSrc, +) !?SwitchProngSrc { for (self.ranges.items) |range| { - if (last.compare(.gte, range.first) and first.compare(.lte, range.last)) { + if (last.compare(.gte, range.first, ty) and first.compare(.lte, range.last, ty)) { return range.src; // They overlap. } } @@ -37,18 +44,18 @@ pub fn add(self: *RangeSet, first: Value, last: Value, src: SwitchProngSrc) !?Sw } /// Assumes a and b do not overlap -fn lessThan(_: void, a: Range, b: Range) bool { - return a.first.compare(.lt, b.first); +fn lessThan(ty: Type, a: Range, b: Range) bool { + return a.first.compare(.lt, b.first, ty); } -pub fn spans(self: *RangeSet, first: Value, last: Value) !bool { +pub fn spans(self: *RangeSet, first: Value, last: Value, ty: Type) !bool { if (self.ranges.items.len == 0) return false; - std.sort.sort(Range, self.ranges.items, {}, lessThan); + std.sort.sort(Range, self.ranges.items, ty, lessThan); - if (!self.ranges.items[0].first.eql(first) or - !self.ranges.items[self.ranges.items.len - 1].last.eql(last)) + if (!self.ranges.items[0].first.eql(first, ty) or + !self.ranges.items[self.ranges.items.len - 1].last.eql(last, ty)) { return false; } diff --git a/src/Sema.zig b/src/Sema.zig index 0da38d9a76..97a6f8323e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -634,7 +634,9 @@ fn analyzeAsType( const wanted_type = Type.initTag(.@"type"); const coerced_inst = try sema.coerce(block, wanted_type, air_inst, src); const val = try sema.resolveConstValue(block, src, coerced_inst); - return val.toType(sema.arena); + var buffer: Value.ToTypeBuffer = undefined; + const ty = val.toType(&buffer); + return ty.copy(sema.arena); } /// May return Value Tags: `variable`, `undef`. @@ -1022,7 +1024,9 @@ fn zirEnumDecl( if (bag != 0) break true; } else false; if (any_values) { - try enum_obj.values.ensureCapacity(&new_decl_arena.allocator, fields_len); + try enum_obj.values.ensureTotalCapacityContext(&new_decl_arena.allocator, fields_len, .{ + .ty = tag_ty, + }); } { @@ -1100,10 +1104,10 @@ fn zirEnumDecl( // that points to this default value expression rather than the struct. // But only resolve the source location if we need to emit a compile error. const tag_val = (try sema.resolveInstConst(block, src, tag_val_ref)).val; - enum_obj.values.putAssumeCapacityNoClobber(tag_val, {}); + enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = tag_ty }); } else if (any_values) { const tag_val = try Value.Tag.int_u64.create(&new_decl_arena.allocator, field_i); - enum_obj.values.putAssumeCapacityNoClobber(tag_val, {}); + enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = tag_ty }); } } @@ -2516,7 +2520,7 @@ fn zirOptionalType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compi const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const child_type = try sema.resolveType(block, src, inst_data.operand); - const opt_type = try sema.mod.optionalType(sema.arena, child_type); + const opt_type = try Module.optionalType(sema.arena, child_type); return sema.addType(opt_type); } @@ -2547,11 +2551,10 @@ fn zirArrayType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE const tracy = trace(@src()); defer tracy.end(); - // TODO these should be lazily evaluated const bin_inst = sema.code.instructions.items(.data)[inst].bin; const len = try sema.resolveInstConst(block, .unneeded, bin_inst.lhs); const elem_type = try sema.resolveType(block, .unneeded, bin_inst.rhs); - const array_ty = try sema.mod.arrayType(sema.arena, len.val.toUnsignedInt(), null, elem_type); + const array_ty = try Module.arrayType(sema.arena, len.val.toUnsignedInt(), null, elem_type); return sema.addType(array_ty); } @@ -2560,13 +2563,12 @@ fn zirArrayTypeSentinel(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) const tracy = trace(@src()); defer tracy.end(); - // TODO these should be lazily evaluated const inst_data = sema.code.instructions.items(.data)[inst].array_type_sentinel; const len = try sema.resolveInstConst(block, .unneeded, inst_data.len); const extra = sema.code.extraData(Zir.Inst.ArrayTypeSentinel, inst_data.payload_index).data; const sentinel = try sema.resolveInstConst(block, .unneeded, extra.sentinel); const elem_type = try sema.resolveType(block, .unneeded, extra.elem_type); - const array_ty = try sema.mod.arrayType(sema.arena, len.val.toUnsignedInt(), sentinel.val, elem_type); + const array_ty = try Module.arrayType(sema.arena, len.val.toUnsignedInt(), sentinel.val, elem_type); return sema.addType(array_ty); } @@ -2599,7 +2601,7 @@ fn zirErrorUnionType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Com error_union.elemType(), }); } - const err_union_ty = try sema.mod.errorUnionType(sema.arena, error_union, payload); + const err_union_ty = try Module.errorUnionType(sema.arena, error_union, payload); return sema.addType(err_union_ty); } @@ -3890,6 +3892,7 @@ fn analyzeSwitch( block, &range_set, item_ref, + operand_ty, src_node_offset, .{ .scalar = scalar_i }, ); @@ -3912,6 +3915,7 @@ fn analyzeSwitch( block, &range_set, item_ref, + operand_ty, src_node_offset, .{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } }, ); @@ -3929,6 +3933,7 @@ fn analyzeSwitch( &range_set, item_first, item_last, + operand_ty, src_node_offset, .{ .range = .{ .prong = multi_i, .item = range_i } }, ); @@ -3945,7 +3950,7 @@ fn analyzeSwitch( const min_int = try operand_ty.minInt(&arena, mod.getTarget()); const max_int = try operand_ty.maxInt(&arena, mod.getTarget()); - if (try range_set.spans(min_int, max_int)) { + if (try range_set.spans(min_int, max_int, operand_ty)) { if (special_prong == .@"else") { return mod.fail( &block.base, @@ -4050,7 +4055,7 @@ fn analyzeSwitch( ); } - var seen_values = ValueSrcMap.init(gpa); + var seen_values = ValueSrcMap.initContext(gpa, .{ .ty = operand_ty }); defer seen_values.deinit(); var extra_index: usize = special.end; @@ -4161,7 +4166,7 @@ fn analyzeSwitch( const item = sema.resolveInst(item_ref); // Validation above ensured these will succeed. const item_val = sema.resolveConstValue(&child_block, .unneeded, item) catch unreachable; - if (operand_val.eql(item_val)) { + if (operand_val.eql(item_val, operand_ty)) { return sema.resolveBlockBody(block, src, &child_block, body, merges); } } @@ -4183,7 +4188,7 @@ fn analyzeSwitch( const item = sema.resolveInst(item_ref); // Validation above ensured these will succeed. const item_val = sema.resolveConstValue(&child_block, .unneeded, item) catch unreachable; - if (operand_val.eql(item_val)) { + if (operand_val.eql(item_val, operand_ty)) { return sema.resolveBlockBody(block, src, &child_block, body, merges); } } @@ -4198,8 +4203,8 @@ fn analyzeSwitch( // Validation above ensured these will succeed. const first_tv = sema.resolveInstConst(&child_block, .unneeded, item_first) catch unreachable; const last_tv = sema.resolveInstConst(&child_block, .unneeded, item_last) catch unreachable; - if (Value.compare(operand_val, .gte, first_tv.val) and - Value.compare(operand_val, .lte, last_tv.val)) + if (Value.compare(operand_val, .gte, first_tv.val, operand_ty) and + Value.compare(operand_val, .lte, last_tv.val, operand_ty)) { return sema.resolveBlockBody(block, src, &child_block, body, merges); } @@ -4450,12 +4455,13 @@ fn validateSwitchRange( range_set: *RangeSet, first_ref: Zir.Inst.Ref, last_ref: Zir.Inst.Ref, + operand_ty: Type, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, ) CompileError!void { const first_val = (try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first)).val; const last_val = (try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last)).val; - const maybe_prev_src = try range_set.add(first_val, last_val, switch_prong_src); + const maybe_prev_src = try range_set.add(first_val, last_val, operand_ty, switch_prong_src); return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); } @@ -4464,11 +4470,12 @@ fn validateSwitchItem( block: *Scope.Block, range_set: *RangeSet, item_ref: Zir.Inst.Ref, + operand_ty: Type, src_node_offset: i32, switch_prong_src: Module.SwitchProngSrc, ) CompileError!void { const item_val = (try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none)).val; - const maybe_prev_src = try range_set.add(item_val, item_val, switch_prong_src); + const maybe_prev_src = try range_set.add(item_val, item_val, operand_ty, switch_prong_src); return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); } @@ -5137,20 +5144,26 @@ fn zirCmp( const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); - if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(resolved_type); - } - if (lhs_val.compare(op, rhs_val)) { - return Air.Inst.Ref.bool_true; + const runtime_src: LazySrcLoc = src: { + if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { + if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { + if (lhs_val.isUndef() or rhs_val.isUndef()) { + return sema.addConstUndef(resolved_type); + } + if (lhs_val.compare(op, rhs_val, resolved_type)) { + return Air.Inst.Ref.bool_true; + } else { + return Air.Inst.Ref.bool_false; + } } else { - return Air.Inst.Ref.bool_false; + break :src rhs_src; } + } else { + break :src lhs_src; } - } + }; + try sema.requireRuntimeBlock(block, runtime_src); - try sema.requireRuntimeBlock(block, src); const tag: Air.Inst.Tag = switch (op) { .lt => .cmp_lt, .lte => .cmp_lte, @@ -5626,7 +5639,7 @@ fn zirPtrTypeSimple(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp const inst_data = sema.code.instructions.items(.data)[inst].ptr_type_simple; const elem_type = try sema.resolveType(block, .unneeded, inst_data.elem_type); - const ty = try sema.mod.ptrType( + const ty = try Module.ptrType( sema.arena, elem_type, null, @@ -5680,7 +5693,7 @@ fn zirPtrType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const elem_type = try sema.resolveType(block, .unneeded, extra.data.elem_type); - const ty = try sema.mod.ptrType( + const ty = try Module.ptrType( sema.arena, elem_type, sentinel, @@ -6569,7 +6582,7 @@ fn panicWithMsg( const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty); const ptr_stack_trace_ty = try Module.simplePtrType(arena, stack_trace_ty, true, .One); const null_stack_trace = try sema.addConstant( - try mod.optionalType(arena, ptr_stack_trace_ty), + try Module.optionalType(arena, ptr_stack_trace_ty), Value.initTag(.null_value), ); const args = try arena.create([2]Air.Inst.Ref); @@ -6713,7 +6726,8 @@ fn fieldVal( }, .Type => { const val = (try sema.resolveDefinedValue(block, object_src, object)).?; - const child_type = try val.toType(arena); + var to_type_buffer: Value.ToTypeBuffer = undefined; + const child_type = val.toType(&to_type_buffer); switch (child_type.zigTypeTag()) { .ErrorSet => { // TODO resolve inferred error sets @@ -6733,7 +6747,7 @@ fn fieldVal( } else (try mod.getErrorValue(field_name)).key; return sema.addConstant( - child_type, + try child_type.copy(arena), try Value.Tag.@"error".create(arena, .{ .name = name }), ); }, @@ -6781,7 +6795,7 @@ fn fieldVal( }; const field_index_u32 = @intCast(u32, field_index); const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32); - return sema.addConstant(child_type, enum_val); + return sema.addConstant(try child_type.copy(arena), enum_val); }, else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), } @@ -6805,7 +6819,6 @@ fn fieldPtr( // in `fieldVal`. This function takes a pointer and returns a pointer. const mod = sema.mod; - const arena = sema.arena; const object_ptr_src = src; // TODO better source location const object_ptr_ty = sema.typeOf(object_ptr); const object_ty = switch (object_ptr_ty.zigTypeTag()) { @@ -6887,7 +6900,8 @@ fn fieldPtr( _ = try sema.resolveConstValue(block, object_ptr_src, object_ptr); const result = try sema.analyzeLoad(block, src, object_ptr, object_ptr_src); const val = (sema.resolveDefinedValue(block, src, result) catch unreachable).?; - const child_type = try val.toType(arena); + var to_type_buffer: Value.ToTypeBuffer = undefined; + const child_type = val.toType(&to_type_buffer); switch (child_type.zigTypeTag()) { .ErrorSet => { // TODO resolve inferred error sets @@ -6902,15 +6916,14 @@ fn fieldPtr( } } return mod.fail(&block.base, src, "no error named '{s}' in '{}'", .{ - field_name, - child_type, + field_name, child_type, }); } else (try mod.getErrorValue(field_name)).key; var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); return sema.analyzeDeclRef(try anon_decl.finish( - child_type, + try child_type.copy(anon_decl.arena()), try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), )); }, @@ -6960,7 +6973,7 @@ fn fieldPtr( var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); return sema.analyzeDeclRef(try anon_decl.finish( - child_type, + try child_type.copy(anon_decl.arena()), try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), )); }, @@ -7352,7 +7365,7 @@ fn coerce( if (src_sentinel) |src_s| { if (dst_sentinel) |dst_s| { - if (src_s.eql(dst_s)) { + if (src_s.eql(dst_s, dst_elem_type)) { return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src); } } @@ -7474,7 +7487,7 @@ fn coerceNum( } } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) { if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { - const res = val.floatCast(sema.arena, dest_type, target) catch |err| switch (err) { + const res = val.floatCast(sema.arena, dest_type) catch |err| switch (err) { error.Overflow => return sema.mod.fail( &block.base, inst_src, @@ -7813,12 +7826,12 @@ fn analyzeSlice( array_type.sentinel() else slice_sentinel; - return_elem_type = try sema.mod.arrayType(sema.arena, len, array_sentinel, elem_type); + return_elem_type = try Module.arrayType(sema.arena, len, array_sentinel, elem_type); return_ptr_size = .One; } } } - const return_type = try sema.mod.ptrType( + const return_type = try Module.ptrType( sema.arena, return_elem_type, if (end_opt == .none) slice_sentinel else null, @@ -7858,39 +7871,42 @@ fn cmpNumeric( if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) { if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) { return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{ - lhs_ty.arrayLen(), - rhs_ty.arrayLen(), + lhs_ty.arrayLen(), rhs_ty.arrayLen(), }); } return sema.mod.fail(&block.base, src, "TODO implement support for vectors in cmpNumeric", .{}); } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) { return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{ - lhs_ty, - rhs_ty, + lhs_ty, rhs_ty, }); } - if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(Type.initTag(.bool)); - } - if (Value.compare(lhs_val, op, rhs_val)) { - return Air.Inst.Ref.bool_true; + const runtime_src: LazySrcLoc = src: { + if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { + if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { + if (lhs_val.isUndef() or rhs_val.isUndef()) { + return sema.addConstUndef(Type.initTag(.bool)); + } + if (Value.compareHetero(lhs_val, op, rhs_val)) { + return Air.Inst.Ref.bool_true; + } else { + return Air.Inst.Ref.bool_false; + } } else { - return Air.Inst.Ref.bool_false; + break :src rhs_src; } + } else { + break :src lhs_src; } - } + }; // TODO handle comparisons against lazy zero values // Some values can be compared against zero without being runtime known or without forcing // a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to // always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout // of this function if we don't need to. + try sema.requireRuntimeBlock(block, runtime_src); - // It must be a runtime comparison. - try sema.requireRuntimeBlock(block, src); // For floats, emit a float comparison instruction. const lhs_is_float = switch (lhs_ty_tag) { .Float, .ComptimeFloat => true, diff --git a/src/type.zig b/src/type.zig index d828df550b..82c28ef398 100644 --- a/src/type.zig +++ b/src/type.zig @@ -426,7 +426,7 @@ pub const Type = extern union { const sentinel_b = info_b.sentinel; if (sentinel_a) |sa| { if (sentinel_b) |sb| { - if (!sa.eql(sb)) + if (!sa.eql(sb, info_a.pointee_type)) return false; } else { return false; @@ -455,13 +455,14 @@ pub const Type = extern union { .Array, .Vector => { if (a.arrayLen() != b.arrayLen()) return false; - if (!a.elemType().eql(b.elemType())) + const elem_ty = a.elemType(); + if (!elem_ty.eql(b.elemType())) return false; const sentinel_a = a.sentinel(); const sentinel_b = b.sentinel(); if (sentinel_a) |sa| { if (sentinel_b) |sb| { - return sa.eql(sb); + return sa.eql(sb, elem_ty); } else { return false; } @@ -2744,29 +2745,37 @@ pub const Type = extern union { return @as(usize, payload.data); } const S = struct { - fn fieldWithRange(int_val: Value, end: usize) ?usize { + fn fieldWithRange(int_ty: Type, int_val: Value, end: usize) ?usize { if (int_val.compareWithZero(.lt)) return null; var end_payload: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = end, }; const end_val = Value.initPayload(&end_payload.base); - if (int_val.compare(.gte, end_val)) return null; + if (int_val.compare(.gte, end_val, int_ty)) return null; return @intCast(usize, int_val.toUnsignedInt()); } }; switch (ty.tag()) { .enum_full, .enum_nonexhaustive => { const enum_full = ty.cast(Payload.EnumFull).?.data; + const tag_ty = enum_full.tag_ty; if (enum_full.values.count() == 0) { - return S.fieldWithRange(enum_tag, enum_full.fields.count()); + return S.fieldWithRange(tag_ty, enum_tag, enum_full.fields.count()); } else { - return enum_full.values.getIndex(enum_tag); + return enum_full.values.getIndexContext(enum_tag, .{ .ty = tag_ty }); } }, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; - return S.fieldWithRange(enum_tag, enum_simple.fields.count()); + const fields_len = enum_simple.fields.count(); + const bits = std.math.log2_int_ceil(usize, fields_len); + var buffer: Payload.Bits = .{ + .base = .{ .tag = .int_unsigned }, + .data = bits, + }; + const tag_ty = Type.initPayload(&buffer.base); + return S.fieldWithRange(tag_ty, enum_tag, fields_len); }, .atomic_ordering, .atomic_rmw_op, @@ -2875,14 +2884,14 @@ pub const Type = extern union { /// Asserts the type is an enum. pub fn enumHasInt(ty: Type, int: Value, target: Target) bool { const S = struct { - fn intInRange(int_val: Value, end: usize) bool { + fn intInRange(tag_ty: Type, int_val: Value, end: usize) bool { if (int_val.compareWithZero(.lt)) return false; var end_payload: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = end, }; const end_val = Value.initPayload(&end_payload.base); - if (int_val.compare(.gte, end_val)) return false; + if (int_val.compare(.gte, end_val, tag_ty)) return false; return true; } }; @@ -2890,15 +2899,23 @@ pub const Type = extern union { .enum_nonexhaustive => return int.intFitsInType(ty, target), .enum_full => { const enum_full = ty.castTag(.enum_full).?.data; + const tag_ty = enum_full.tag_ty; if (enum_full.values.count() == 0) { - return S.intInRange(int, enum_full.fields.count()); + return S.intInRange(tag_ty, int, enum_full.fields.count()); } else { - return enum_full.values.contains(int); + return enum_full.values.containsContext(int, .{ .ty = tag_ty }); } }, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; - return S.intInRange(int, enum_simple.fields.count()); + const fields_len = enum_simple.fields.count(); + const bits = std.math.log2_int_ceil(usize, fields_len); + var buffer: Payload.Bits = .{ + .base = .{ .tag = .int_unsigned }, + .data = bits, + }; + const tag_ty = Type.initPayload(&buffer.base); + return S.intInRange(tag_ty, int, fields_len); }, .atomic_ordering, .atomic_rmw_op, diff --git a/src/value.zig b/src/value.zig index 32be34ee2c..93d6625e7c 100644 --- a/src/value.zig +++ b/src/value.zig @@ -653,8 +653,10 @@ pub const Value = extern union { unreachable; } + pub const ToTypeBuffer = Type.Payload.Bits; + /// Asserts that the value is representable as a type. - pub fn toType(self: Value, allocator: *Allocator) !Type { + pub fn toType(self: Value, buffer: *ToTypeBuffer) Type { return switch (self.tag()) { .ty => self.castTag(.ty).?.data, .u1_type => Type.initTag(.u1), @@ -714,14 +716,13 @@ pub const Value = extern union { .int_type => { const payload = self.castTag(.int_type).?.data; - const new = try allocator.create(Type.Payload.Bits); - new.* = .{ + buffer.* = .{ .base = .{ .tag = if (payload.signed) .int_signed else .int_unsigned, }, .data = payload.bits, }; - return Type.initPayload(&new.base); + return Type.initPayload(&buffer.base); }, .undef, @@ -958,9 +959,8 @@ pub const Value = extern union { /// Converts an integer or a float to a float. /// Returns `error.Overflow` if the value does not fit in the new type. - pub fn floatCast(self: Value, allocator: *Allocator, ty: Type, target: Target) !Value { - _ = target; - switch (ty.tag()) { + pub fn floatCast(self: Value, allocator: *Allocator, dest_ty: Type) !Value { + switch (dest_ty.tag()) { .f16 => { @panic("TODO add __trunctfhf2 to compiler-rt"); //const res = try Value.Tag.float_16.create(allocator, self.toFloat(f16)); @@ -970,13 +970,13 @@ pub const Value = extern union { }, .f32 => { const res = try Value.Tag.float_32.create(allocator, self.toFloat(f32)); - if (!self.eql(res)) + if (!self.eql(res, dest_ty)) return error.Overflow; return res; }, .f64 => { const res = try Value.Tag.float_64.create(allocator, self.toFloat(f64)); - if (!self.eql(res)) + if (!self.eql(res, dest_ty)) return error.Overflow; return res; }, @@ -1083,12 +1083,18 @@ pub const Value = extern union { return lhs_bigint.order(rhs_bigint); } - /// Asserts the value is comparable. - pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value) bool { + /// Asserts the value is comparable. Does not take a type parameter because it supports + /// comparisons between heterogeneous types. + pub fn compareHetero(lhs: Value, op: std.math.CompareOperator, rhs: Value) bool { + return order(lhs, rhs).compare(op); + } + + /// Asserts the value is comparable. Both operands have type `ty`. + pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, ty: Type) bool { return switch (op) { - .eq => lhs.eql(rhs), - .neq => !lhs.eql(rhs), - else => order(lhs, rhs).compare(op), + .eq => lhs.eql(rhs, ty), + .neq => !lhs.eql(rhs, ty), + else => compareHetero(lhs, op, rhs), }; } @@ -1097,11 +1103,11 @@ pub const Value = extern union { return orderAgainstZero(lhs).compare(op); } - /// TODO we can't compare value equality without also knowing the type to treat - /// the values as - pub fn eql(a: Value, b: Value) bool { + pub fn eql(a: Value, b: Value, ty: Type) bool { const a_tag = a.tag(); const b_tag = b.tag(); + assert(a_tag != .undef); + assert(b_tag != .undef); if (a_tag == b_tag) { switch (a_tag) { .void_value, .null_value => return true, @@ -1118,230 +1124,106 @@ pub const Value = extern union { else => {}, } } - if (a.isType() and b.isType()) { - // 128 bytes should be enough to hold both types - var buf: [128]u8 = undefined; - var fib = std.heap.FixedBufferAllocator.init(&buf); - const a_type = a.toType(&fib.allocator) catch unreachable; - const b_type = b.toType(&fib.allocator) catch unreachable; + if (ty.zigTypeTag() == .Type) { + var buf_a: ToTypeBuffer = undefined; + var buf_b: ToTypeBuffer = undefined; + const a_type = a.toType(&buf_a); + const b_type = b.toType(&buf_b); return a_type.eql(b_type); } return order(a, b).compare(.eq); } - pub fn hash_u32(self: Value) u32 { - return @truncate(u32, self.hash()); - } - - /// TODO we can't hash without also knowing the type of the value. - /// we have to hash as if there were a canonical value memory layout. - pub fn hash(self: Value) u64 { - var hasher = std.hash.Wyhash.init(0); + pub const ArrayHashContext = struct { + ty: Type, - switch (self.tag()) { - .u1_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .anyframe_type, - .const_slice_u8_type, - .enum_literal_type, - .ty, - .abi_align_default, - => { - // Directly return Type.hash, toType can only fail for .int_type. - var allocator = std.heap.FixedBufferAllocator.init(&[_]u8{}); - return (self.toType(&allocator.allocator) catch unreachable).hash(); - }, - .int_type => { - const payload = self.castTag(.int_type).?.data; - var int_payload = Type.Payload.Bits{ - .base = .{ - .tag = if (payload.signed) .int_signed else .int_unsigned, - }, - .data = payload.bits, - }; - return Type.initPayload(&int_payload.base).hash(); - }, + pub fn hash(self: @This(), v: Value) u32 { + const other_context: HashContext = .{ .ty = self.ty }; + return @truncate(u32, other_context.hash(v)); + } + pub fn eql(self: @This(), a: Value, b: Value) bool { + return a.eql(b, self.ty); + } + }; - .empty_struct_value, - .empty_array, - => {}, + pub const HashContext = struct { + ty: Type, - .undef, - .null_value, - .void_value, - .unreachable_value, - => std.hash.autoHash(&hasher, self.tag()), + pub fn hash(self: @This(), v: Value) u64 { + var hasher = std.hash.Wyhash.init(0); - .zero, .bool_false => std.hash.autoHash(&hasher, @as(u64, 0)), - .one, .bool_true => std.hash.autoHash(&hasher, @as(u64, 1)), + switch (self.ty.zigTypeTag()) { + .BoundFn => unreachable, // TODO remove this from the language - .float_16, .float_32, .float_64, .float_128 => { - @panic("TODO implement Value.hash for floats"); - }, + .Void, + .NoReturn, + .Undefined, + .Null, + => {}, - .enum_literal => { - const payload = self.castTag(.enum_literal).?; - hasher.update(payload.data); - }, - .enum_field_index => { - const payload = self.castTag(.enum_field_index).?; - std.hash.autoHash(&hasher, payload.data); - }, - .bytes => { - const payload = self.castTag(.bytes).?; - hasher.update(payload.data); - }, - .repeated => { - @panic("TODO Value.hash for repeated"); - }, - .array => { - @panic("TODO Value.hash for array"); - }, - .slice => { - @panic("TODO Value.hash for slice"); - }, - .eu_payload_ptr => { - @panic("TODO Value.hash for eu_payload_ptr"); - }, - .int_u64 => { - const payload = self.castTag(.int_u64).?; - std.hash.autoHash(&hasher, payload.data); - }, - .int_i64 => { - const payload = self.castTag(.int_i64).?; - std.hash.autoHash(&hasher, payload.data); - }, - .comptime_alloc => { - const payload = self.castTag(.comptime_alloc).?; - std.hash.autoHash(&hasher, payload.data.val.hash()); - }, - .int_big_positive, .int_big_negative => { - var space: BigIntSpace = undefined; - const big = self.toBigInt(&space); - if (big.limbs.len == 1) { - // handle like {u,i}64 to ensure same hash as with Int{i,u}64 - if (big.positive) { - std.hash.autoHash(&hasher, @as(u64, big.limbs[0])); - } else { - std.hash.autoHash(&hasher, @as(u64, @bitCast(usize, -@bitCast(isize, big.limbs[0])))); - } - } else { + .Type => { + var buf: ToTypeBuffer = undefined; + return v.toType(&buf).hash(); + }, + .Bool => { + std.hash.autoHash(&hasher, v.toBool()); + }, + .Int, .ComptimeInt => { + var space: BigIntSpace = undefined; + const big = v.toBigInt(&space); std.hash.autoHash(&hasher, big.positive); for (big.limbs) |limb| { std.hash.autoHash(&hasher, limb); } - } - }, - .elem_ptr => { - const payload = self.castTag(.elem_ptr).?.data; - std.hash.autoHash(&hasher, payload.array_ptr.hash()); - std.hash.autoHash(&hasher, payload.index); - }, - .field_ptr => { - const payload = self.castTag(.field_ptr).?.data; - std.hash.autoHash(&hasher, payload.container_ptr.hash()); - std.hash.autoHash(&hasher, payload.field_index); - }, - .decl_ref => { - const decl = self.castTag(.decl_ref).?.data; - std.hash.autoHash(&hasher, decl); - }, - .function => { - const func = self.castTag(.function).?.data; - std.hash.autoHash(&hasher, func); - }, - .extern_fn => { - const decl = self.castTag(.extern_fn).?.data; - std.hash.autoHash(&hasher, decl); - }, - .variable => { - const variable = self.castTag(.variable).?.data; - std.hash.autoHash(&hasher, variable); - }, - .@"error" => { - const payload = self.castTag(.@"error").?.data; - hasher.update(payload.name); - }, - .error_union => { - const payload = self.castTag(.error_union).?.data; - std.hash.autoHash(&hasher, payload.hash()); - }, - .inferred_alloc => unreachable, - - .manyptr_u8_type, - .manyptr_const_u8_type, - .atomic_ordering_type, - .atomic_rmw_op_type, - .calling_convention_type, - .float_mode_type, - .reduce_op_type, - .call_options_type, - .export_options_type, - .extern_options_type, - .@"struct", - .@"union", - => @panic("TODO this hash function looks pretty broken. audit it"), + }, + .Float, .ComptimeFloat => { + @panic("TODO implement hashing float values"); + }, + .Pointer => { + @panic("TODO implement hashing pointer values"); + }, + .Array, .Vector => { + @panic("TODO implement hashing array/vector values"); + }, + .Struct => { + @panic("TODO implement hashing struct values"); + }, + .Optional => { + @panic("TODO implement hashing optional values"); + }, + .ErrorUnion => { + @panic("TODO implement hashing error union values"); + }, + .ErrorSet => { + @panic("TODO implement hashing error set values"); + }, + .Enum => { + @panic("TODO implement hashing enum values"); + }, + .Union => { + @panic("TODO implement hashing union values"); + }, + .Fn => { + @panic("TODO implement hashing function values"); + }, + .Opaque => { + @panic("TODO implement hashing opaque values"); + }, + .Frame => { + @panic("TODO implement hashing frame values"); + }, + .AnyFrame => { + @panic("TODO implement hashing anyframe values"); + }, + .EnumLiteral => { + @panic("TODO implement hashing enum literal values"); + }, + } + return hasher.final(); } - return hasher.final(); - } - pub const ArrayHashContext = struct { - pub fn hash(self: @This(), v: Value) u32 { - _ = self; - return v.hash_u32(); - } - pub fn eql(self: @This(), a: Value, b: Value) bool { - _ = self; - return a.eql(b); - } - }; - pub const HashContext = struct { - pub fn hash(self: @This(), v: Value) u64 { - _ = self; - return v.hash(); - } pub fn eql(self: @This(), a: Value, b: Value) bool { - _ = self; - return a.eql(b); + return a.eql(b, self.ty); } }; @@ -1508,111 +1390,6 @@ pub const Value = extern union { }; } - /// Valid for all types. Asserts the value is not undefined. - /// TODO this function is a code smell and should be deleted - fn isType(self: Value) bool { - return switch (self.tag()) { - .ty, - .int_type, - .u1_type, - .u8_type, - .i8_type, - .u16_type, - .i16_type, - .u32_type, - .i32_type, - .u64_type, - .i64_type, - .u128_type, - .i128_type, - .usize_type, - .isize_type, - .c_short_type, - .c_ushort_type, - .c_int_type, - .c_uint_type, - .c_long_type, - .c_ulong_type, - .c_longlong_type, - .c_ulonglong_type, - .c_longdouble_type, - .f16_type, - .f32_type, - .f64_type, - .f128_type, - .c_void_type, - .bool_type, - .void_type, - .type_type, - .anyerror_type, - .comptime_int_type, - .comptime_float_type, - .noreturn_type, - .null_type, - .undefined_type, - .fn_noreturn_no_args_type, - .fn_void_no_args_type, - .fn_naked_noreturn_no_args_type, - .fn_ccc_void_no_args_type, - .single_const_pointer_to_comptime_int_type, - .anyframe_type, - .const_slice_u8_type, - .enum_literal_type, - .manyptr_u8_type, - .manyptr_const_u8_type, - .atomic_ordering_type, - .atomic_rmw_op_type, - .calling_convention_type, - .float_mode_type, - .reduce_op_type, - .call_options_type, - .export_options_type, - .extern_options_type, - => true, - - .zero, - .one, - .empty_array, - .bool_true, - .bool_false, - .function, - .extern_fn, - .variable, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .comptime_alloc, - .decl_ref, - .elem_ptr, - .field_ptr, - .bytes, - .repeated, - .array, - .slice, - .float_16, - .float_32, - .float_64, - .float_128, - .void_value, - .enum_literal, - .enum_field_index, - .@"error", - .error_union, - .empty_struct_value, - .@"struct", - .@"union", - .null_value, - .abi_align_default, - .eu_payload_ptr, - => false, - - .undef => unreachable, - .unreachable_value => unreachable, - .inferred_alloc => unreachable, - }; - } - /// This type is not copyable since it may contain pointers to its inner data. pub const Payload = struct { tag: Tag, @@ -1806,27 +1583,3 @@ pub const Value = extern union { limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb, }; }; - -test "hash same value different representation" { - const zero_1 = Value.initTag(.zero); - var payload_1 = Value.Payload.U64{ - .base = .{ .tag = .int_u64 }, - .data = 0, - }; - const zero_2 = Value.initPayload(&payload_1.base); - try std.testing.expectEqual(zero_1.hash(), zero_2.hash()); - - var payload_2 = Value.Payload.I64{ - .base = .{ .tag = .int_i64 }, - .data = 0, - }; - const zero_3 = Value.initPayload(&payload_2.base); - try std.testing.expectEqual(zero_2.hash(), zero_3.hash()); - - var payload_3 = Value.Payload.BigInt{ - .base = .{ .tag = .int_big_negative }, - .data = &[_]std.math.big.Limb{0}, - }; - const zero_4 = Value.initPayload(&payload_3.base); - try std.testing.expectEqual(zero_3.hash(), zero_4.hash()); -} -- cgit v1.2.3 From ddf14323ea9b2c75ac5ed286525d27730a192b53 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Aug 2021 16:13:58 -0700 Subject: stage2: implement `@truncate` --- src/Air.zig | 11 +- src/Liveness.zig | 1 + src/Module.zig | 238 ------------------------------------------ src/Sema.zig | 104 ++++++++++++------ src/codegen.zig | 14 +++ src/codegen/c.zig | 15 ++- src/codegen/llvm.zig | 11 ++ src/codegen/llvm/bindings.zig | 8 ++ src/print_air.zig | 1 + src/value.zig | 238 ++++++++++++++++++++++++++++++++++++++++++ test/behavior/basic.zig | 11 ++ test/behavior/misc.zig | 7 -- 12 files changed, 380 insertions(+), 279 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Air.zig b/src/Air.zig index d202c079bc..d923bf0b02 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -206,10 +206,16 @@ pub const Inst = struct { /// Convert from one float type to another. /// Uses the `ty_op` field. floatcast, - /// TODO audit uses of this. We should have explicit instructions for integer - /// widening and truncating. + /// Returns an integer with a different type than the operand. The new type may have + /// fewer, the same, or more bits than the operand type. However, the instruction + /// guarantees that the same integer value fits in both types. + /// See `trunc` for integer truncation. /// Uses the `ty_op` field. intcast, + /// Truncate higher bits from an integer, resulting in an integer with the same + /// sign but an equal or smaller number of bits. + /// Uses the `ty_op` field. + trunc, /// ?T => T. If the value is null, undefined behavior. /// Uses the `ty_op` field. optional_payload, @@ -452,6 +458,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .load, .floatcast, .intcast, + .trunc, .optional_payload, .optional_payload_ptr, .wrap_optional, diff --git a/src/Liveness.zig b/src/Liveness.zig index 7ba062fa31..4e22febc5a 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -264,6 +264,7 @@ fn analyzeInst( .load, .floatcast, .intcast, + .trunc, .optional_payload, .optional_payload_ptr, .wrap_optional, diff --git a/src/Module.zig b/src/Module.zig index d87f20621c..84b721369d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4033,244 +4033,6 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) Co return error.AnalysisFail; } -pub fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try allocator.alloc( - std.math.big.Limb, - std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, - ); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - result_bigint.add(lhs_bigint, rhs_bigint); - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - if (result_bigint.positive) { - return Value.Tag.int_big_positive.create(allocator, result_limbs); - } else { - return Value.Tag.int_big_negative.create(allocator, result_limbs); - } -} - -pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try allocator.alloc( - std.math.big.Limb, - std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, - ); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - result_bigint.sub(lhs_bigint, rhs_bigint); - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - if (result_bigint.positive) { - return Value.Tag.int_big_positive.create(allocator, result_limbs); - } else { - return Value.Tag.int_big_negative.create(allocator, result_limbs); - } -} - -pub fn intDiv(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs_q = try allocator.alloc( - std.math.big.Limb, - lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, - ); - const limbs_r = try allocator.alloc( - std.math.big.Limb, - lhs_bigint.limbs.len, - ); - const limbs_buffer = try allocator.alloc( - std.math.big.Limb, - std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), - ); - var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; - var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; - result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); - const result_limbs = result_q.limbs[0..result_q.len]; - - if (result_q.positive) { - return Value.Tag.int_big_positive.create(allocator, result_limbs); - } else { - return Value.Tag.int_big_negative.create(allocator, result_limbs); - } -} - -pub fn intMul(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // TODO is this a performance issue? maybe we should try the operation without - // resorting to BigInt first. - var lhs_space: Value.BigIntSpace = undefined; - var rhs_space: Value.BigIntSpace = undefined; - const lhs_bigint = lhs.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try allocator.alloc( - std.math.big.Limb, - lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, - ); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - var limbs_buffer = try allocator.alloc( - std.math.big.Limb, - std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1), - ); - defer allocator.free(limbs_buffer); - result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator); - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - if (result_bigint.positive) { - return Value.Tag.int_big_positive.create(allocator, result_limbs); - } else { - return Value.Tag.int_big_negative.create(allocator, result_limbs); - } -} - -pub fn floatAdd( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - switch (float_type.tag()) { - .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val + rhs_val); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val + rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val + rhs_val); - }, - .f128, .comptime_float, .c_longdouble => { - const lhs_val = lhs.toFloat(f128); - const rhs_val = rhs.toFloat(f128); - return Value.Tag.float_128.create(arena, lhs_val + rhs_val); - }, - else => unreachable, - } -} - -pub fn floatSub( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - switch (float_type.tag()) { - .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val - rhs_val); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val - rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val - rhs_val); - }, - .f128, .comptime_float, .c_longdouble => { - const lhs_val = lhs.toFloat(f128); - const rhs_val = rhs.toFloat(f128); - return Value.Tag.float_128.create(arena, lhs_val - rhs_val); - }, - else => unreachable, - } -} - -pub fn floatDiv( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - switch (float_type.tag()) { - .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val / rhs_val); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val / rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val / rhs_val); - }, - .f128, .comptime_float, .c_longdouble => { - const lhs_val = lhs.toFloat(f128); - const rhs_val = rhs.toFloat(f128); - return Value.Tag.float_128.create(arena, lhs_val / rhs_val); - }, - else => unreachable, - } -} - -pub fn floatMul( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - switch (float_type.tag()) { - .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val * rhs_val); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val * rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val * rhs_val); - }, - .f128, .comptime_float, .c_longdouble => { - const lhs_val = lhs.toFloat(f128); - const rhs_val = rhs.toFloat(f128); - return Value.Tag.float_128.create(arena, lhs_val * rhs_val); - }, - else => unreachable, - } -} - pub fn simplePtrType( arena: *Allocator, elem_ty: Type, diff --git a/src/Sema.zig b/src/Sema.zig index 5ad590be6a..6b281f8569 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3479,27 +3479,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs); const operand = sema.resolveInst(extra.rhs); - const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { - .ComptimeInt => true, - .Int => false, - else => return sema.mod.fail( - &block.base, - dest_ty_src, - "expected integer type, found '{}'", - .{dest_type}, - ), - }; - - const operand_ty = sema.typeOf(operand); - switch (operand_ty.zigTypeTag()) { - .ComptimeInt, .Int => {}, - else => return sema.mod.fail( - &block.base, - operand_src, - "expected integer type, found '{}'", - .{operand_ty}, - ), - } + const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_type); + _ = try sema.requireIntegerType(block, operand_src, sema.typeOf(operand)); if (try sema.isComptimeKnown(block, operand_src, operand)) { return sema.coerce(block, dest_type, operand, operand_src); @@ -4951,30 +4932,30 @@ fn analyzeArithmetic( const value = switch (zir_tag) { .add => blk: { const val = if (is_int) - try Module.intAdd(sema.arena, lhs_val, rhs_val) + try lhs_val.intAdd(rhs_val, sema.arena) else - try Module.floatAdd(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena); break :blk val; }, .sub => blk: { const val = if (is_int) - try Module.intSub(sema.arena, lhs_val, rhs_val) + try lhs_val.intSub(rhs_val, sema.arena) else - try Module.floatSub(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatSub(rhs_val, scalar_type, sema.arena); break :blk val; }, .div => blk: { const val = if (is_int) - try Module.intDiv(sema.arena, lhs_val, rhs_val) + try lhs_val.intDiv(rhs_val, sema.arena) else - try Module.floatDiv(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena); break :blk val; }, .mul => blk: { const val = if (is_int) - try Module.intMul(sema.arena, lhs_val, rhs_val) + try lhs_val.intMul(rhs_val, sema.arena) else - try Module.floatMul(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatMul(rhs_val, scalar_type, sema.arena); break :blk val; }, else => return sema.mod.fail(&block.base, src, "TODO Implement arithmetic operand '{s}'", .{@tagName(zir_tag)}), @@ -6173,7 +6154,62 @@ fn zirPtrCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr fn zirTruncate(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirTruncate", .{}); + const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); + const operand = sema.resolveInst(extra.rhs); + const operand_ty = sema.typeOf(operand); + const mod = sema.mod; + const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_ty); + const src_is_comptime_int = try sema.requireIntegerType(block, operand_src, operand_ty); + + if (dest_is_comptime_int) { + return sema.coerce(block, dest_ty, operand, operand_src); + } + + const target = mod.getTarget(); + const src_info = operand_ty.intInfo(target); + const dest_info = dest_ty.intInfo(target); + + if (src_info.bits == 0 or dest_info.bits == 0) { + return sema.addConstant(dest_ty, Value.initTag(.zero)); + } + + if (!src_is_comptime_int) { + if (src_info.signedness != dest_info.signedness) { + return mod.fail(&block.base, operand_src, "expected {s} integer type, found '{}'", .{ + @tagName(dest_info.signedness), operand_ty, + }); + } + if (src_info.bits > 0 and src_info.bits < dest_info.bits) { + const msg = msg: { + const msg = try mod.errMsg( + &block.base, + src, + "destination type '{}' has more bits than source type '{}'", + .{ dest_ty, operand_ty }, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(&block.base, dest_ty_src, msg, "destination type has {d} bits", .{ + dest_info.bits, + }); + try mod.errNote(&block.base, operand_src, msg, "source type has {d} bits", .{ + src_info.bits, + }); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + } + } + + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(dest_ty); + return sema.addConstant(dest_ty, try val.intTrunc(sema.arena, dest_info.bits)); + } + + try sema.requireRuntimeBlock(block, src); + return block.addTyOp(.trunc, dest_ty, operand); } fn zirAlignCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6594,6 +6630,14 @@ fn requireRuntimeBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void try sema.requireFunctionBlock(block, src); } +fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !bool { + switch (ty.zigTypeTag()) { + .ComptimeInt => return true, + .Int => return false, + else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}), + } +} + fn validateVarType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void { if (!ty.isValidVarType(false)) { return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{ty}); diff --git a/src/codegen.zig b/src/codegen.zig index 8c56ab4431..3864479d2d 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -835,6 +835,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .dbg_stmt => try self.airDbgStmt(inst), .floatcast => try self.airFloatCast(inst), .intcast => try self.airIntCast(inst), + .trunc => try self.airTrunc(inst), .bool_to_int => try self.airBoolToInt(inst), .is_non_null => try self.airIsNonNull(inst), .is_non_null_ptr => try self.airIsNonNullPtr(inst), @@ -1109,6 +1110,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } + fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + if (self.liveness.isUnused(inst)) + return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none }); + + const operand = try self.resolveInst(ty_op.operand); + _ = operand; + const result: MCValue = switch (arch) { + else => return self.fail("TODO implement trunc for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + fn airBoolToInt(self: *Self, inst: Air.Inst.Index) !void { const un_op = self.air.instructions.items(.data)[inst].un_op; const operand = try self.resolveInst(un_op); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 826b73317c..22420aca45 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -900,6 +900,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .call => try airCall(o, inst), .dbg_stmt => try airDbgStmt(o, inst), .intcast => try airIntCast(o, inst), + .trunc => try airTrunc(o, inst), .bool_to_int => try airBoolToInt(o, inst), .load => try airLoad(o, inst), .ret => try airRet(o, inst), @@ -1038,7 +1039,7 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { return CValue.none; const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const from = try o.resolveInst(ty_op.operand); + const operand = try o.resolveInst(ty_op.operand); const writer = o.writer(); const inst_ty = o.air.typeOfIndex(inst); @@ -1046,11 +1047,21 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { try writer.writeAll(" = ("); try o.dg.renderType(writer, inst_ty); try writer.writeAll(")"); - try o.writeCValue(writer, from); + try o.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } +fn airTrunc(o: *Object, inst: Air.Inst.Index) !CValue { + if (o.liveness.isUnused(inst)) + return CValue.none; + + const ty_op = o.air.instructions.items(.data)[inst].ty_op; + const operand = try o.resolveInst(ty_op.operand); + _ = operand; + return o.dg.fail("TODO: C backend: airTrunc", .{}); +} + fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue { if (o.liveness.isUnused(inst)) return CValue.none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 961ed7ee99..8b3edcfe23 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -960,6 +960,7 @@ pub const FuncGen = struct { .call => try self.airCall(inst), .cond_br => try self.airCondBr(inst), .intcast => try self.airIntCast(inst), + .trunc => try self.airTrunc(inst), .floatcast => try self.airFloatCast(inst), .ptrtoint => try self.airPtrToInt(inst), .load => try self.airLoad(inst), @@ -1615,6 +1616,16 @@ pub const FuncGen = struct { return self.builder.buildIntCast2(operand, try self.dg.llvmType(inst_ty), llvm.Bool.fromBool(signed), ""); } + fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst)); + return self.builder.buildTrunc(operand, dest_llvm_ty, ""); + } + fn airFloatCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 0977d6128d..4af3cadd84 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -423,6 +423,14 @@ pub const Builder = opaque { Idx: c_uint, Name: [*:0]const u8, ) *const Value; + + pub const buildTrunc = LLVMBuildTrunc; + extern fn LLVMBuildTrunc( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; }; pub const IntPredicate = enum(c_int) { diff --git a/src/print_air.zig b/src/print_air.zig index 5d77c303bb..00317b26e8 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -151,6 +151,7 @@ const Writer = struct { .load, .floatcast, .intcast, + .trunc, .optional_payload, .optional_payload_ptr, .wrap_optional, diff --git a/src/value.zig b/src/value.zig index a0a7c0650c..134b51e494 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1407,6 +1407,244 @@ pub const Value = extern union { }; } + pub fn intAdd(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try allocator.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.add(lhs_bigint, rhs_bigint); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intSub(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try allocator.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.sub(lhs_bigint, rhs_bigint); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intDiv(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs_q = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + const limbs_r = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len, + ); + const limbs_buffer = try allocator.alloc( + std.math.big.Limb, + std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; + var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; + result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); + const result_limbs = result_q.limbs[0..result_q.len]; + + if (result_q.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intMul(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + var limbs_buffer = try allocator.alloc( + std.math.big.Limb, + std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1), + ); + defer allocator.free(limbs_buffer); + result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intTrunc(val: Value, arena: *Allocator, bits: u16) !Value { + const x = val.toUnsignedInt(); // TODO: implement comptime truncate on big ints + if (bits == 64) return val; + const mask = (@as(u64, 1) << @intCast(u6, bits)) - 1; + const truncated = x & mask; + return Tag.int_u64.create(arena, truncated); + } + + pub fn floatAdd( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val + rhs_val); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val + rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val + rhs_val); + }, + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val + rhs_val); + }, + else => unreachable, + } + } + + pub fn floatSub( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val - rhs_val); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val - rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val - rhs_val); + }, + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val - rhs_val); + }, + else => unreachable, + } + } + + pub fn floatDiv( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val / rhs_val); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val / rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val / rhs_val); + }, + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val / rhs_val); + }, + else => unreachable, + } + } + + pub fn floatMul( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val * rhs_val); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val * rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val * rhs_val); + }, + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val * rhs_val); + }, + else => unreachable, + } + } + /// This type is not copyable since it may contain pointers to its inner data. pub const Payload = struct { tag: Tag, diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 8f0f20cdf5..87de1c9fe5 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -1,3 +1,6 @@ +const std = @import("std"); +const expect = std.testing.expect; + // normal comment /// this is a documentation comment @@ -7,3 +10,11 @@ fn emptyFunctionWithComments() void {} test "empty function with comments" { emptyFunctionWithComments(); } + +test "truncate" { + try expect(testTruncate(0x10fd) == 0xfd); + comptime try expect(testTruncate(0x10fd) == 0xfd); +} +fn testTruncate(x: u32) u8 { + return @truncate(u8, x); +} diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig index ba38d6327c..700e043313 100644 --- a/test/behavior/misc.zig +++ b/test/behavior/misc.zig @@ -5,13 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings; const mem = std.mem; const builtin = @import("builtin"); -test "truncate" { - try expect(testTruncate(0x10fd) == 0xfd); -} -fn testTruncate(x: u32) u8 { - return @truncate(u8, x); -} - fn first4KeysOfHomeRow() []const u8 { return "aoeu"; } -- cgit v1.2.3 From 1472dc3ddb6fd7932ff530e7a2fd3f0185c7353f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 2 Aug 2021 20:35:55 -0700 Subject: stage2: update ZIR for generic functions ZIR encoding for functions is changed in preparation for generic function support. As an example: ```zig const std = @import("std"); const expect = std.testing.expect; test "example" { var x: usize = 0; x += checkSize(i32, 1); x += checkSize(bool, true); try expect(x == 5); } fn checkSize(comptime T: type, x: T) usize { _ = x; return @sizeOf(T); } ``` Previous ZIR for the `checkSize` function: ```zir [165] checkSize line(10) hash(0226f62e189fd0b1c5fca02cf4617562): %55 = block_inline({ %56 = decl_val("T") token_offset:11:35 %57 = as_node(@Ref.type_type, %56) node_offset:11:35 %69 = extended(func([comptime @Ref.type_type, %57], @Ref.usize_type, { %58 = arg("T") token_offset:11:23 %59 = as_node(@Ref.type_type, %58) node_offset:11:35 %60 = arg("x") token_offset:11:32 %61 = dbg_stmt(11, 4) ``` ZIR for the `checkSize` function after this commit: ```zir [157] checkSize line(10) hash(0226f62e189fd0b1c5fca02cf4617562): %55 = block_inline({ %56 = param_comptime("T", @Ref.type_type) token_offset:11:23 %57 = as_node(@Ref.type_type, %56) node_offset:11:35 %58 = param("x", %57) token_offset:11:32 %67 = func(@Ref.usize_type, { %59 = dbg_stmt(11, 4) ``` Noted differences: * Previously the type expression was redundantly repeated. * Previously the parameter names were redundantly stored in the ZIR extra array. * Instead of `arg` ZIR instructions as the first instructions within a function body, they are now outside the function body, in the same block as the `func` instruction. There are variants: - param - param_comptime - param_anytype - param_anytype_comptime * The param instructions additionally encode the type. * Because of the param instructions, the `func` instruction no longer encodes the list of parameter types or the comptime bits. It's implied that Sema will collect the parameters so that when a `func` instruction is encountered, they will be implicitly used to construct the function's type. This is so that we can satisfy all 3 ways of performing semantic analysis on a function: 1. runtime: Sema will insert AIR arg instructions for each parameter, and insert into the Sema inst_map ZIR param => AIR arg. 2. comptime/inline: Sema will insert into the inst_map ZIR param => callsite arguments. 3. generic: Sema will map *only the comptime* ZIR param instructions to the AIR instructions for the comptime arguments at the callsite, and then re-run Sema for the function's Decl. This will produce a new function which is the monomorphized function. Additionally: * AstGen: Update usage of deprecated `ensureCapacity` to `ensureUnusedCapacity` or `ensureTotalCapacity`. * Introduce `Type.fnInfo` for getting a bunch of data about a function type at once, and use it in `analyzeCall`. This commit starts a branch to implement generic functions in stage2. Test regressions have not been addressed yet. --- BRANCH_TODO | 9 ++ src/AstGen.zig | 424 +++++++++++++++++++++++++-------------------------------- src/Module.zig | 2 +- src/Sema.zig | 193 ++++++++++++++++---------- src/Zir.zig | 121 ++++++++-------- src/type.zig | 60 ++++++-- 6 files changed, 431 insertions(+), 378 deletions(-) create mode 100644 BRANCH_TODO (limited to 'src/Module.zig') diff --git a/BRANCH_TODO b/BRANCH_TODO new file mode 100644 index 0000000000..bc0a67f799 --- /dev/null +++ b/BRANCH_TODO @@ -0,0 +1,9 @@ +* update arg instructions: + - runtime function call inserts AIR arg instructions and Sema map items for them + - comptime/inline function call inserts Sema map items for the args + - generic instantiation inserts Sema map items for the comptime args only, re-runs the + Decl ZIR to get the new Fn. +* generic function call where it makes a new function +* memoize the instantiation in a table +* anytype with next parameter expression using it +* comptime anytype diff --git a/src/AstGen.zig b/src/AstGen.zig index 20480ab33b..0b78c839a0 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -42,7 +42,7 @@ const InnerError = error{ OutOfMemory, AnalysisFail }; fn addExtra(astgen: *AstGen, extra: anytype) Allocator.Error!u32 { const fields = std.meta.fields(@TypeOf(extra)); - try astgen.extra.ensureCapacity(astgen.gpa, astgen.extra.items.len + fields.len); + try astgen.extra.ensureUnusedCapacity(astgen.gpa, fields.len); return addExtraAssumeCapacity(astgen, extra); } @@ -259,6 +259,7 @@ pub const ResultLoc = union(enum) { pub const align_rl: ResultLoc = .{ .ty = .u16_type }; pub const bool_rl: ResultLoc = .{ .ty = .bool_type }; +pub const type_rl: ResultLoc = .{ .ty = .type_type }; fn typeExpr(gz: *GenZir, scope: *Scope, type_node: ast.Node.Index) InnerError!Zir.Inst.Ref { const prev_force_comptime = gz.force_comptime; @@ -1036,7 +1037,6 @@ fn fnProtoExpr( fn_proto: ast.full.FnProto, ) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; - const gpa = astgen.gpa; const tree = astgen.tree; const token_tags = tree.tokens.items(.tag); @@ -1046,71 +1046,53 @@ fn fnProtoExpr( }; assert(!is_extern); - // The AST params array does not contain anytype and ... parameters. - // We must iterate to count how many param types to allocate. - const param_count = blk: { - var count: usize = 0; - var it = fn_proto.iterate(tree.*); - while (it.next()) |param| { - if (param.anytype_ellipsis3) |token| switch (token_tags[token]) { - .ellipsis3 => break, - .keyword_anytype => {}, - else => unreachable, - }; - count += 1; - } - break :blk count; - }; - const param_types = try gpa.alloc(Zir.Inst.Ref, param_count); - defer gpa.free(param_types); - - const bits_per_param = 1; - const params_per_u32 = 32 / bits_per_param; - // We only need this if there are greater than params_per_u32 fields. - var bit_bag = ArrayListUnmanaged(u32){}; - defer bit_bag.deinit(gpa); - var cur_bit_bag: u32 = 0; - var is_var_args = false; - { + const is_var_args = is_var_args: { var param_type_i: usize = 0; var it = fn_proto.iterate(tree.*); while (it.next()) |param| : (param_type_i += 1) { - if (param_type_i % params_per_u32 == 0 and param_type_i != 0) { - try bit_bag.append(gpa, cur_bit_bag); - cur_bit_bag = 0; - } const is_comptime = if (param.comptime_noalias) |token| token_tags[token] == .keyword_comptime else false; - cur_bit_bag = (cur_bit_bag >> bits_per_param) | - (@as(u32, @boolToInt(is_comptime)) << 31); - if (param.anytype_ellipsis3) |token| { + const is_anytype = if (param.anytype_ellipsis3) |token| blk: { switch (token_tags[token]) { - .keyword_anytype => { - param_types[param_type_i] = .none; - continue; - }, - .ellipsis3 => { - is_var_args = true; - break; - }, + .keyword_anytype => break :blk true, + .ellipsis3 => break :is_var_args true, else => unreachable, } - } - const param_type_node = param.type_expr; - assert(param_type_node != 0); - param_types[param_type_i] = - try expr(gz, scope, .{ .ty = .type_type }, param_type_node); - } - assert(param_type_i == param_count); + } else false; + + const param_name: u32 = if (param.name_token) |name_token| blk: { + if (mem.eql(u8, "_", tree.tokenSlice(name_token))) + break :blk 0; + + break :blk try astgen.identAsString(name_token); + } else 0; - const empty_slot_count = params_per_u32 - (param_type_i % params_per_u32); - if (empty_slot_count < params_per_u32) { - cur_bit_bag >>= @intCast(u5, empty_slot_count * bits_per_param); + if (is_anytype) { + const name_token = param.name_token orelse param.anytype_ellipsis3.?; + + const tag: Zir.Inst.Tag = if (is_comptime) + .param_anytype_comptime + else + .param_anytype; + _ = try gz.addStrTok(tag, param_name, name_token); + } else { + const param_type_node = param.type_expr; + assert(param_type_node != 0); + const param_type = try expr(gz, scope, type_rl, param_type_node); + const main_tokens = tree.nodes.items(.main_token); + const name_token = param.name_token orelse main_tokens[param_type_node]; + const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param; + _ = try gz.addPlTok(tag, name_token, Zir.Inst.Param{ + .name = param_name, + .ty = param_type, + }); + } } - } + break :is_var_args false; + }; const align_inst: Zir.Inst.Ref = if (fn_proto.ast.align_expr == 0) .none else inst: { break :inst try expr(gz, scope, align_rl, fn_proto.ast.align_expr); @@ -1144,7 +1126,6 @@ fn fnProtoExpr( const result = try gz.addFunc(.{ .src_node = fn_proto.ast.proto_node, .ret_ty = return_type_inst, - .param_types = param_types, .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = align_inst, @@ -1153,8 +1134,6 @@ fn fnProtoExpr( .is_inferred_error = false, .is_test = false, .is_extern = false, - .cur_bit_bag = cur_bit_bag, - .bit_bag = bit_bag.items, }); return rvalue(gz, rl, result, fn_proto.ast.proto_node); } @@ -1447,8 +1426,8 @@ fn structInitExprRlNone( const init_inst = try gz.addPlNode(tag, node, Zir.Inst.StructInitAnon{ .fields_len = @intCast(u32, fields_list.len), }); - try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + - fields_list.len * @typeInfo(Zir.Inst.StructInitAnon.Item).Struct.fields.len); + try astgen.extra.ensureUnusedCapacity(gpa, fields_list.len * + @typeInfo(Zir.Inst.StructInitAnon.Item).Struct.fields.len); for (fields_list) |field| { _ = gz.astgen.addExtraAssumeCapacity(field); } @@ -1520,8 +1499,8 @@ fn structInitExprRlTy( const init_inst = try gz.addPlNode(tag, node, Zir.Inst.StructInit{ .fields_len = @intCast(u32, fields_list.len), }); - try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + - fields_list.len * @typeInfo(Zir.Inst.StructInit.Item).Struct.fields.len); + try astgen.extra.ensureUnusedCapacity(gpa, fields_list.len * + @typeInfo(Zir.Inst.StructInit.Item).Struct.fields.len); for (fields_list) |field| { _ = gz.astgen.addExtraAssumeCapacity(field); } @@ -1918,7 +1897,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) Inner // ZIR instructions that might be a type other than `noreturn` or `void`. .add, .addwrap, - .arg, + .param, + .param_comptime, + .param_anytype, + .param_anytype_comptime, .alloc, .alloc_mut, .alloc_comptime, @@ -2488,7 +2470,7 @@ fn varDecl( // Move the init_scope instructions into the parent scope, swapping // store_to_block_ptr for store_to_inferred_ptr. const expected_len = parent_zir.items.len + init_scope.instructions.items.len; - try parent_zir.ensureCapacity(gpa, expected_len); + try parent_zir.ensureTotalCapacity(gpa, expected_len); for (init_scope.instructions.items) |src_inst| { if (zir_tags[src_inst] == .store_to_block_ptr) { if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) { @@ -2750,10 +2732,10 @@ fn ptrType( } const gpa = gz.astgen.gpa; - try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); - try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + - @typeInfo(Zir.Inst.PtrType).Struct.fields.len + trailing_count); + try gz.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.PtrType).Struct.fields.len + + trailing_count); const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.PtrType{ .elem_type = elem_type }); if (sentinel_ref != .none) { @@ -2899,6 +2881,16 @@ fn fnDecl( }; defer decl_gz.instructions.deinit(gpa); + var fn_gz: GenZir = .{ + .force_comptime = false, + .in_defer = false, + .decl_node_index = fn_proto.ast.proto_node, + .decl_line = decl_gz.decl_line, + .parent = &decl_gz.base, + .astgen = astgen, + }; + defer fn_gz.instructions.deinit(gpa); + // TODO: support noinline const is_pub = fn_proto.visib_token != null; const is_export = blk: { @@ -2922,71 +2914,76 @@ fn fnDecl( try wip_decls.next(gpa, is_pub, is_export, align_inst != .none, section_inst != .none); - // The AST params array does not contain anytype and ... parameters. - // We must iterate to count how many param types to allocate. - const param_count = blk: { - var count: usize = 0; - var it = fn_proto.iterate(tree.*); - while (it.next()) |param| { - if (param.anytype_ellipsis3) |token| switch (token_tags[token]) { - .ellipsis3 => break, - .keyword_anytype => {}, - else => unreachable, - }; - count += 1; - } - break :blk count; - }; - const param_types = try gpa.alloc(Zir.Inst.Ref, param_count); - defer gpa.free(param_types); - - const bits_per_param = 1; - const params_per_u32 = 32 / bits_per_param; - // We only need this if there are greater than params_per_u32 fields. - var bit_bag = ArrayListUnmanaged(u32){}; - defer bit_bag.deinit(gpa); - var cur_bit_bag: u32 = 0; - var is_var_args = false; - { + var params_scope = &fn_gz.base; + const is_var_args = is_var_args: { var param_type_i: usize = 0; var it = fn_proto.iterate(tree.*); while (it.next()) |param| : (param_type_i += 1) { - if (param_type_i % params_per_u32 == 0 and param_type_i != 0) { - try bit_bag.append(gpa, cur_bit_bag); - cur_bit_bag = 0; - } const is_comptime = if (param.comptime_noalias) |token| token_tags[token] == .keyword_comptime else false; - cur_bit_bag = (cur_bit_bag >> bits_per_param) | - (@as(u32, @boolToInt(is_comptime)) << 31); - if (param.anytype_ellipsis3) |token| { + const is_anytype = if (param.anytype_ellipsis3) |token| blk: { switch (token_tags[token]) { - .keyword_anytype => { - param_types[param_type_i] = .none; - continue; - }, - .ellipsis3 => { - is_var_args = true; - break; - }, + .keyword_anytype => break :blk true, + .ellipsis3 => break :is_var_args true, else => unreachable, } - } - const param_type_node = param.type_expr; - assert(param_type_node != 0); - param_types[param_type_i] = - try expr(&decl_gz, &decl_gz.base, .{ .ty = .type_type }, param_type_node); - } - assert(param_type_i == param_count); + } else false; + + const param_name: u32 = if (param.name_token) |name_token| blk: { + if (mem.eql(u8, "_", tree.tokenSlice(name_token))) + break :blk 0; + + const param_name = try astgen.identAsString(name_token); + if (!is_extern) { + try astgen.detectLocalShadowing(params_scope, param_name, name_token); + } + break :blk param_name; + } else if (!is_extern) { + if (param.anytype_ellipsis3) |tok| { + return astgen.failTok(tok, "missing parameter name", .{}); + } else { + return astgen.failNode(param.type_expr, "missing parameter name", .{}); + } + } else 0; + + const param_inst = if (is_anytype) param: { + const name_token = param.name_token orelse param.anytype_ellipsis3.?; + const tag: Zir.Inst.Tag = if (is_comptime) + .param_anytype_comptime + else + .param_anytype; + break :param try decl_gz.addStrTok(tag, param_name, name_token); + } else param: { + const param_type_node = param.type_expr; + assert(param_type_node != 0); + const param_type = try expr(&decl_gz, params_scope, type_rl, param_type_node); + const main_tokens = tree.nodes.items(.main_token); + const name_token = param.name_token orelse main_tokens[param_type_node]; + const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param; + break :param try decl_gz.addPlTok(tag, name_token, Zir.Inst.Param{ + .name = param_name, + .ty = param_type, + }); + }; + + if (param_name == 0) continue; - const empty_slot_count = params_per_u32 - (param_type_i % params_per_u32); - if (empty_slot_count < params_per_u32) { - cur_bit_bag >>= @intCast(u5, empty_slot_count * bits_per_param); + const sub_scope = try astgen.arena.create(Scope.LocalVal); + sub_scope.* = .{ + .parent = params_scope, + .gen_zir = &decl_gz, + .name = param_name, + .inst = param_inst, + .token_src = param.name_token.?, + .id_cat = .@"function parameter", + }; + params_scope = &sub_scope.base; } - } + break :is_var_args false; + }; const lib_name: u32 = if (fn_proto.lib_name) |lib_name_token| blk: { const lib_name_str = try astgen.strLitAsString(lib_name_token); @@ -2998,7 +2995,7 @@ fn fnDecl( const return_type_inst = try AstGen.expr( &decl_gz, - &decl_gz.base, + params_scope, .{ .ty = .type_type }, fn_proto.ast.return_type, ); @@ -3014,7 +3011,7 @@ fn fnDecl( } break :blk try AstGen.expr( &decl_gz, - &decl_gz.base, + params_scope, .{ .ty = .calling_convention_type }, fn_proto.ast.callconv_expr, ); @@ -3038,7 +3035,6 @@ fn fnDecl( break :func try decl_gz.addFunc(.{ .src_node = decl_node, .ret_ty = return_type_inst, - .param_types = param_types, .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = .none, // passed in the per-decl data @@ -3047,75 +3043,18 @@ fn fnDecl( .is_inferred_error = false, .is_test = false, .is_extern = true, - .cur_bit_bag = cur_bit_bag, - .bit_bag = bit_bag.items, }); } else func: { if (is_var_args) { return astgen.failTok(fn_proto.ast.fn_token, "non-extern function is variadic", .{}); } - var fn_gz: GenZir = .{ - .force_comptime = false, - .in_defer = false, - .decl_node_index = fn_proto.ast.proto_node, - .decl_line = decl_gz.decl_line, - .parent = &decl_gz.base, - .astgen = astgen, - }; - defer fn_gz.instructions.deinit(gpa); - const prev_fn_block = astgen.fn_block; astgen.fn_block = &fn_gz; defer astgen.fn_block = prev_fn_block; - // Iterate over the parameters. We put the param names as the first N - // items inside `extra` so that debug info later can refer to the parameter names - // even while the respective source code is unloaded. - try astgen.extra.ensureUnusedCapacity(gpa, param_count); - - { - var params_scope = &fn_gz.base; - var i: usize = 0; - var it = fn_proto.iterate(tree.*); - while (it.next()) |param| : (i += 1) { - const name_token = param.name_token orelse { - if (param.anytype_ellipsis3) |tok| { - return astgen.failTok(tok, "missing parameter name", .{}); - } else { - return astgen.failNode(param.type_expr, "missing parameter name", .{}); - } - }; - if (param.type_expr != 0) - _ = try typeExpr(&fn_gz, params_scope, param.type_expr); - if (mem.eql(u8, "_", tree.tokenSlice(name_token))) - continue; - const param_name = try astgen.identAsString(name_token); - // Create an arg instruction. This is needed to emit a semantic analysis - // error for shadowing decls. - try astgen.detectLocalShadowing(params_scope, param_name, name_token); - const arg_inst = try fn_gz.addStrTok(.arg, param_name, name_token); - const sub_scope = try astgen.arena.create(Scope.LocalVal); - sub_scope.* = .{ - .parent = params_scope, - .gen_zir = &fn_gz, - .name = param_name, - .inst = arg_inst, - .token_src = name_token, - .id_cat = .@"function parameter", - }; - params_scope = &sub_scope.base; - - // Additionally put the param name into `string_bytes` and reference it with - // `extra` so that we have access to the data in codegen, for debug info. - const str_index = try astgen.identAsString(name_token); - try astgen.extra.append(astgen.gpa, str_index); - } - _ = try typeExpr(&fn_gz, params_scope, fn_proto.ast.return_type); - - _ = try expr(&fn_gz, params_scope, .none, body_node); - try checkUsed(gz, &fn_gz.base, params_scope); - } + _ = try expr(&fn_gz, params_scope, .none, body_node); + try checkUsed(gz, &fn_gz.base, params_scope); const need_implicit_ret = blk: { if (fn_gz.instructions.items.len == 0) @@ -3133,7 +3072,6 @@ fn fnDecl( break :func try decl_gz.addFunc(.{ .src_node = decl_node, .ret_ty = return_type_inst, - .param_types = param_types, .body = fn_gz.instructions.items, .cc = cc, .align_inst = .none, // passed in the per-decl data @@ -3142,8 +3080,6 @@ fn fnDecl( .is_inferred_error = is_inferred_error, .is_test = false, .is_extern = false, - .cur_bit_bag = cur_bit_bag, - .bit_bag = bit_bag.items, }); }; @@ -3480,7 +3416,6 @@ fn testDecl( const func_inst = try decl_block.addFunc(.{ .src_node = node, .ret_ty = .void_type, - .param_types = &[0]Zir.Inst.Ref{}, .body = fn_block.instructions.items, .cc = .none, .align_inst = .none, @@ -3489,8 +3424,6 @@ fn testDecl( .is_inferred_error = true, .is_test = true, .is_extern = false, - .cur_bit_bag = 0, - .bit_bag = &.{}, }); _ = try decl_block.addBreak(.break_inline, block_inst, func_inst); @@ -4238,7 +4171,7 @@ fn containerDecl( var fields_data = ArrayListUnmanaged(u32){}; defer fields_data.deinit(gpa); - try fields_data.ensureCapacity(gpa, counts.total_fields + counts.values); + try fields_data.ensureTotalCapacity(gpa, counts.total_fields + counts.values); // We only need this if there are greater than 32 fields. var bit_bag = ArrayListUnmanaged(u32){}; @@ -5184,8 +5117,7 @@ fn setCondBrPayload( ) !void { const astgen = then_scope.astgen; - try astgen.extra.ensureCapacity(astgen.gpa, astgen.extra.items.len + - @typeInfo(Zir.Inst.CondBr).Struct.fields.len + + try astgen.extra.ensureUnusedCapacity(astgen.gpa, @typeInfo(Zir.Inst.CondBr).Struct.fields.len + then_scope.instructions.items.len + else_scope.instructions.items.len); const zir_datas = astgen.instructions.items(.data); @@ -5839,10 +5771,9 @@ fn switchExpr( _ = try case_scope.addBreak(.@"break", switch_block, case_result); } // Documentation for this: `Zir.Inst.SwitchBlock` and `Zir.Inst.SwitchBlockMulti`. - try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len + + try scalar_cases_payload.ensureUnusedCapacity(gpa, case_scope.instructions.items.len + 3 + // operand, scalar_cases_len, else body len - @boolToInt(multi_cases_len != 0) + - case_scope.instructions.items.len); + @boolToInt(multi_cases_len != 0)); scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand)); scalar_cases_payload.appendAssumeCapacity(scalar_cases_len); if (multi_cases_len != 0) { @@ -5852,9 +5783,11 @@ fn switchExpr( scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items); } else { // Documentation for this: `Zir.Inst.SwitchBlock` and `Zir.Inst.SwitchBlockMulti`. - try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len + - 2 + // operand, scalar_cases_len - @boolToInt(multi_cases_len != 0)); + try scalar_cases_payload.ensureUnusedCapacity( + gpa, + @as(usize, 2) + // operand, scalar_cases_len + @boolToInt(multi_cases_len != 0), + ); scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand)); scalar_cases_payload.appendAssumeCapacity(scalar_cases_len); if (multi_cases_len != 0) { @@ -5975,8 +5908,8 @@ fn switchExpr( block_scope.break_count += 1; _ = try case_scope.addBreak(.@"break", switch_block, case_result); } - try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len + - 2 + case_scope.instructions.items.len); + try scalar_cases_payload.ensureUnusedCapacity(gpa, 2 + + case_scope.instructions.items.len); scalar_cases_payload.appendAssumeCapacity(@enumToInt(item_inst)); scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len)); scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items); @@ -6012,8 +5945,8 @@ fn switchExpr( const payload_index = astgen.extra.items.len; const zir_datas = astgen.instructions.items(.data); zir_datas[switch_block].pl_node.payload_index = @intCast(u32, payload_index); - try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + - scalar_cases_payload.items.len + multi_cases_payload.items.len); + try astgen.extra.ensureUnusedCapacity(gpa, scalar_cases_payload.items.len + + multi_cases_payload.items.len); const strat = rl.strategy(&block_scope); switch (strat.tag) { .break_operand => { @@ -8659,7 +8592,7 @@ fn failNodeNotes( } const notes_index: u32 = if (notes.len != 0) blk: { const notes_start = astgen.extra.items.len; - try astgen.extra.ensureCapacity(astgen.gpa, notes_start + 1 + notes.len); + try astgen.extra.ensureTotalCapacity(astgen.gpa, notes_start + 1 + notes.len); astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len)); astgen.extra.appendSliceAssumeCapacity(notes); break :blk @intCast(u32, notes_start); @@ -8700,7 +8633,7 @@ fn failTokNotes( } const notes_index: u32 = if (notes.len != 0) blk: { const notes_start = astgen.extra.items.len; - try astgen.extra.ensureCapacity(astgen.gpa, notes_start + 1 + notes.len); + try astgen.extra.ensureTotalCapacity(astgen.gpa, notes_start + 1 + notes.len); astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len)); astgen.extra.appendSliceAssumeCapacity(notes); break :blk @intCast(u32, notes_start); @@ -8864,7 +8797,7 @@ fn strLitNodeAsString(astgen: *AstGen, node: ast.Node.Index) !IndexSlice { while (tok_i <= end) : (tok_i += 1) { const slice = tree.tokenSlice(tok_i); const line_bytes = slice[2 .. slice.len - 1]; - try string_bytes.ensureCapacity(gpa, string_bytes.items.len + line_bytes.len + 1); + try string_bytes.ensureUnusedCapacity(gpa, line_bytes.len + 1); string_bytes.appendAssumeCapacity('\n'); string_bytes.appendSliceAssumeCapacity(line_bytes); } @@ -9131,8 +9064,8 @@ const GenZir = struct { fn setBoolBrBody(gz: GenZir, inst: Zir.Inst.Index) !void { const gpa = gz.astgen.gpa; - try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + - @typeInfo(Zir.Inst.Block).Struct.fields.len + gz.instructions.items.len); + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Block).Struct.fields.len + + gz.instructions.items.len); const zir_datas = gz.astgen.instructions.items(.data); zir_datas[inst].bool_br.payload_index = gz.astgen.addExtraAssumeCapacity( Zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) }, @@ -9142,8 +9075,8 @@ const GenZir = struct { fn setBlockBody(gz: GenZir, inst: Zir.Inst.Index) !void { const gpa = gz.astgen.gpa; - try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + - @typeInfo(Zir.Inst.Block).Struct.fields.len + gz.instructions.items.len); + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Block).Struct.fields.len + + gz.instructions.items.len); const zir_datas = gz.astgen.instructions.items(.data); zir_datas[inst].pl_node.payload_index = gz.astgen.addExtraAssumeCapacity( Zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) }, @@ -9155,8 +9088,8 @@ const GenZir = struct { /// `store_to_block_ptr` instructions with lhs set to .none. fn setBlockBodyEliding(gz: GenZir, inst: Zir.Inst.Index) !void { const gpa = gz.astgen.gpa; - try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + - @typeInfo(Zir.Inst.Block).Struct.fields.len + gz.instructions.items.len); + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Block).Struct.fields.len + + gz.instructions.items.len); const zir_datas = gz.astgen.instructions.items(.data); const zir_tags = gz.astgen.instructions.items(.tag); const block_pl_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Block{ @@ -9177,7 +9110,6 @@ const GenZir = struct { fn addFunc(gz: *GenZir, args: struct { src_node: ast.Node.Index, - param_types: []const Zir.Inst.Ref, body: []const Zir.Inst.Index, ret_ty: Zir.Inst.Ref, cc: Zir.Inst.Ref, @@ -9187,8 +9119,6 @@ const GenZir = struct { is_inferred_error: bool, is_test: bool, is_extern: bool, - cur_bit_bag: u32, - bit_bag: []const u32, }) !Zir.Inst.Ref { assert(args.src_node != 0); assert(args.ret_ty != .none); @@ -9226,19 +9156,14 @@ const GenZir = struct { src_locs = &src_locs_buffer; } - const any_are_comptime = args.cur_bit_bag != 0 or for (args.bit_bag) |x| { - if (x != 0) break true; - } else false; - if (args.cc != .none or args.lib_name != 0 or args.is_var_args or args.is_test or args.align_inst != .none or - args.is_extern or any_are_comptime) + args.is_extern) { try astgen.extra.ensureUnusedCapacity( gpa, @typeInfo(Zir.Inst.ExtendedFunc).Struct.fields.len + - @boolToInt(any_are_comptime) + args.bit_bag.len + - args.param_types.len + args.body.len + src_locs.len + + args.body.len + src_locs.len + @boolToInt(args.lib_name != 0) + @boolToInt(args.align_inst != .none) + @boolToInt(args.cc != .none), @@ -9246,7 +9171,6 @@ const GenZir = struct { const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{ .src_node = gz.nodeIndexToRelative(args.src_node), .return_type = args.ret_ty, - .param_types_len = @intCast(u32, args.param_types.len), .body_len = @intCast(u32, args.body.len), }); if (args.lib_name != 0) { @@ -9258,11 +9182,6 @@ const GenZir = struct { if (args.align_inst != .none) { astgen.extra.appendAssumeCapacity(@enumToInt(args.align_inst)); } - if (any_are_comptime) { - astgen.extra.appendSliceAssumeCapacity(args.bit_bag); // Likely empty. - astgen.extra.appendAssumeCapacity(args.cur_bit_bag); - } - astgen.appendRefsAssumeCapacity(args.param_types); astgen.extra.appendSliceAssumeCapacity(args.body); astgen.extra.appendSliceAssumeCapacity(src_locs); @@ -9279,7 +9198,6 @@ const GenZir = struct { .has_align = args.align_inst != .none, .is_test = args.is_test, .is_extern = args.is_extern, - .has_comptime_bits = any_are_comptime, }), .operand = payload_index, } }, @@ -9290,15 +9208,13 @@ const GenZir = struct { try gz.astgen.extra.ensureUnusedCapacity( gpa, @typeInfo(Zir.Inst.Func).Struct.fields.len + - args.param_types.len + args.body.len + src_locs.len, + args.body.len + src_locs.len, ); const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{ .return_type = args.ret_ty, - .param_types_len = @intCast(u32, args.param_types.len), .body_len = @intCast(u32, args.body.len), }); - gz.astgen.appendRefsAssumeCapacity(args.param_types); gz.astgen.extra.appendSliceAssumeCapacity(args.body); gz.astgen.extra.appendSliceAssumeCapacity(src_locs); @@ -9380,10 +9296,10 @@ const GenZir = struct { assert(callee != .none); assert(src_node != 0); const gpa = gz.astgen.gpa; - try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); - try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + - @typeInfo(Zir.Inst.Call).Struct.fields.len + args.len); + try gz.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Call).Struct.fields.len + + args.len); const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Call{ .callee = callee, @@ -9412,8 +9328,8 @@ const GenZir = struct { ) !Zir.Inst.Index { assert(lhs != .none); const gpa = gz.astgen.gpa; - try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); + try gz.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); gz.astgen.instructions.appendAssumeCapacity(.{ @@ -9486,8 +9402,8 @@ const GenZir = struct { extra: anytype, ) !Zir.Inst.Ref { const gpa = gz.astgen.gpa; - try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); + try gz.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); const payload_index = try gz.astgen.addExtra(extra); const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); @@ -9502,6 +9418,30 @@ const GenZir = struct { return indexToRef(new_index); } + fn addPlTok( + gz: *GenZir, + tag: Zir.Inst.Tag, + /// Absolute token index. This function does the conversion to Decl offset. + abs_tok_index: ast.TokenIndex, + extra: anytype, + ) !Zir.Inst.Ref { + const gpa = gz.astgen.gpa; + try gz.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); + + const payload_index = try gz.astgen.addExtra(extra); + const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); + gz.astgen.instructions.appendAssumeCapacity(.{ + .tag = tag, + .data = .{ .pl_tok = .{ + .src_tok = gz.tokenIndexToRelative(abs_tok_index), + .payload_index = payload_index, + } }, + }); + gz.instructions.appendAssumeCapacity(new_index); + return indexToRef(new_index); + } + fn addExtendedPayload( gz: *GenZir, opcode: Zir.Inst.Extended, @@ -9509,8 +9449,8 @@ const GenZir = struct { ) !Zir.Inst.Ref { const gpa = gz.astgen.gpa; - try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); + try gz.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); const payload_index = try gz.astgen.addExtra(extra); const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); @@ -9566,8 +9506,8 @@ const GenZir = struct { elem_type: Zir.Inst.Ref, ) !Zir.Inst.Ref { const gpa = gz.astgen.gpa; - try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); + try gz.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); const payload_index = try gz.astgen.addExtra(Zir.Inst.ArrayTypeSentinel{ .sentinel = sentinel, @@ -9822,7 +9762,7 @@ const GenZir = struct { /// Leaves the `payload_index` field undefined. fn addCondBr(gz: *GenZir, tag: Zir.Inst.Tag, node: ast.Node.Index) !Zir.Inst.Index { const gpa = gz.astgen.gpa; - try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); + try gz.instructions.ensureUnusedCapacity(gpa, 1); const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); try gz.astgen.instructions.append(gpa, .{ .tag = tag, diff --git a/src/Module.zig b/src/Module.zig index 84b721369d..fa8b4ca768 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3714,7 +3714,7 @@ fn markOutdatedDecl(mod: *Module, decl: *Decl) !void { decl.analysis = .outdated; } -fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node.Index) !*Decl { +pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node.Index) !*Decl { // If we have emit-h then we must allocate a bigger structure to store the emit-h state. const new_decl: *Decl = if (mod.emit_h != null) blk: { const parent_struct = try mod.gpa.create(DeclPlusEmitH); diff --git a/src/Sema.zig b/src/Sema.zig index 753ef8fb9c..5fd3c149a2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -44,6 +44,7 @@ branch_count: u32 = 0, /// contain a mapped source location. src: LazySrcLoc = .{ .token_offset = 0 }, next_arg_index: usize = 0, +params: std.ArrayListUnmanaged(Param) = .{}, decl_val_table: std.AutoHashMapUnmanaged(*Decl, Air.Inst.Ref) = .{}, const std = @import("std"); @@ -68,6 +69,13 @@ const LazySrcLoc = Module.LazySrcLoc; const RangeSet = @import("RangeSet.zig"); const target_util = @import("target.zig"); +const Param = struct { + name: [:0]const u8, + /// `none` means `anytype`. + ty: Air.Inst.Ref, + is_comptime: bool, +}; + pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref); pub fn deinit(sema: *Sema) void { @@ -91,8 +99,7 @@ pub fn analyzeFnBody( .func, .func_inferred => blk: { const inst_data = datas[fn_body_inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index); - const param_types_len = extra.data.param_types_len; - const body = sema.code.extra[extra.end + param_types_len ..][0..extra.data.body_len]; + const body = sema.code.extra[extra.end..][0..extra.data.body_len]; break :blk body; }, .extended => blk: { @@ -104,10 +111,6 @@ pub fn analyzeFnBody( extra_index += @boolToInt(small.has_lib_name); extra_index += @boolToInt(small.has_cc); extra_index += @boolToInt(small.has_align); - if (small.has_comptime_bits) { - extra_index += (extra.data.param_types_len + 31) / 32; - } - extra_index += extra.data.param_types_len; const body = sema.code.extra[extra_index..][0..extra.data.body_len]; break :blk body; }, @@ -162,7 +165,6 @@ pub fn analyzeBody( const inst = body[i]; const air_inst: Air.Inst.Ref = switch (tags[inst]) { // zig fmt: off - .arg => try sema.zirArg(block, inst), .alloc => try sema.zirAlloc(block, inst), .alloc_inferred => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_const)), .alloc_inferred_mut => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_mut)), @@ -404,6 +406,26 @@ pub fn analyzeBody( // continue the loop. // We also know that they cannot be referenced later, so we avoid // putting them into the map. + .param => { + try sema.zirParam(inst, false); + i += 1; + continue; + }, + .param_comptime => { + try sema.zirParam(inst, true); + i += 1; + continue; + }, + .param_anytype => { + try sema.zirParamAnytype(inst, false); + i += 1; + continue; + }, + .param_anytype_comptime => { + try sema.zirParamAnytype(inst, true); + i += 1; + continue; + }, .breakpoint => { try sema.zirBreakpoint(block, inst); i += 1; @@ -1358,23 +1380,34 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co return sema.analyzeLoad(block, src, result_ptr, result_ptr_src); } -fn zirArg(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].str_tok; - const arg_name = inst_data.get(sema.code); - const arg_index = sema.next_arg_index; - sema.next_arg_index += 1; +fn zirParam(sema: *Sema, inst: Zir.Inst.Index, is_comptime: bool) CompileError!void { + const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; + const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; + const param_name = sema.code.nullTerminatedString(extra.name); - // TODO check if arg_name shadows a Decl - _ = arg_name; + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. - if (block.inlining) |_| { - return sema.param_inst_list[arg_index]; - } + const param_ty = sema.resolveInst(extra.ty); + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = param_ty, + .is_comptime = is_comptime, + }); +} + +fn zirParamAnytype(sema: *Sema, inst: Zir.Inst.Index, is_comptime: bool) CompileError!void { + const inst_data = sema.code.instructions.items(.data)[inst].str_tok; + const param_name = inst_data.get(sema.code); - // Set the name of the Air.Arg instruction for use by codegen debug info. - const air_arg = sema.param_inst_list[arg_index]; - sema.air_instructions.items(.data)[Air.refToIndex(air_arg).?].ty_str.str = inst_data.start; - return air_arg; + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = .none, + .is_comptime = is_comptime, + }); } fn zirAllocExtended( @@ -2395,26 +2428,29 @@ fn analyzeCall( ensure_result_used: bool, args: []const Air.Inst.Ref, ) CompileError!Air.Inst.Ref { + const mod = sema.mod; + const func_ty = sema.typeOf(func); if (func_ty.zigTypeTag() != .Fn) - return sema.mod.fail(&block.base, func_src, "type '{}' not a function", .{func_ty}); + return mod.fail(&block.base, func_src, "type '{}' not a function", .{func_ty}); - const cc = func_ty.fnCallingConvention(); + const func_ty_info = func_ty.fnInfo(); + const cc = func_ty_info.cc; if (cc == .Naked) { // TODO add error note: declared here - return sema.mod.fail( + return mod.fail( &block.base, func_src, "unable to call function with naked calling convention", .{}, ); } - const fn_params_len = func_ty.fnParamLen(); - if (func_ty.fnIsVarArgs()) { + const fn_params_len = func_ty_info.param_types.len; + if (func_ty_info.is_var_args) { assert(cc == .C); if (args.len < fn_params_len) { // TODO add error note: declared here - return sema.mod.fail( + return mod.fail( &block.base, func_src, "expected at least {d} argument(s), found {d}", @@ -2423,7 +2459,7 @@ fn analyzeCall( } } else if (fn_params_len != args.len) { // TODO add error note: declared here - return sema.mod.fail( + return mod.fail( &block.base, func_src, "expected {d} argument(s), found {d}", @@ -2442,7 +2478,7 @@ fn analyzeCall( .never_inline, .no_async, .always_tail, - => return sema.mod.fail(&block.base, call_src, "TODO implement call with modifier {}", .{ + => return mod.fail(&block.base, call_src, "TODO implement call with modifier {}", .{ modifier, }), } @@ -2451,12 +2487,12 @@ fn analyzeCall( const is_comptime_call = block.is_comptime or modifier == .compile_time; const is_inline_call = is_comptime_call or modifier == .always_inline or - func_ty.fnCallingConvention() == .Inline; + func_ty_info.cc == .Inline; const result: Air.Inst.Ref = if (is_inline_call) res: { const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = switch (func_val.tag()) { .function => func_val.castTag(.function).?.data, - .extern_fn => return sema.mod.fail(&block.base, call_src, "{s} call of extern function", .{ + .extern_fn => return mod.fail(&block.base, call_src, "{s} call of extern function", .{ @as([]const u8, if (is_comptime_call) "comptime" else "inline"), }), else => unreachable, @@ -2535,10 +2571,46 @@ fn analyzeCall( const result = try sema.analyzeBlockBody(block, call_src, &child_block, merges); break :res result; + } else if (func_ty_info.is_generic) { + const func_val = try sema.resolveConstValue(block, func_src, func); + const module_fn = func_val.castTag(.function).?.data; + // Check the Module's generic function map with an adapted context, so that we + // can match against `args` rather than doing the work below to create a generic Scope + // only to junk it if it matches an existing instantiation. + // TODO + + // Create a Decl for the new function. + const generic_namespace = try sema.arena.create(Module.Scope.Namespace); + generic_namespace.* = .{ + .parent = block.src_decl.namespace, + .file_scope = block.src_decl.namespace.file_scope, + .ty = func_ty, + }; + const new_decl = try mod.allocateNewDecl(generic_namespace, module_fn.owner_decl.src_node); + _ = new_decl; + + // Iterate over the parameters that are comptime, evaluating their type expressions + // inside a Scope which contains the previous parameters. + //for (args) |arg, arg_i| { + //} + + // Create a new Fn with only the runtime-known parameters. + // TODO + + // Populate the Decl ty/val with the function and its type. + // TODO + + // Queue up a `codegen_func` work item for the new Fn, making sure it will have + // `analyzeFnBody` called with the Scope which contains the comptime parameters. + // TODO + + // Save it into the Module's generic function map. + // TODO + + // Call it the same as a runtime function. + // TODO + return mod.fail(&block.base, func_src, "TODO implement generic fn call", .{}); } else res: { - if (func_ty.fnIsGeneric()) { - return sema.mod.fail(&block.base, func_src, "TODO implement generic fn call", .{}); - } try sema.requireRuntimeBlock(block, call_src); try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + args.len); @@ -3186,13 +3258,12 @@ fn zirFunc( const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index); - const param_types = sema.code.refSlice(extra.end, extra.data.param_types_len); var body_inst: Zir.Inst.Index = 0; var src_locs: Zir.Inst.Func.SrcLocs = undefined; if (extra.data.body_len != 0) { body_inst = inst; - const extra_index = extra.end + extra.data.param_types_len + extra.data.body_len; + const extra_index = extra.end + extra.data.body_len; src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } @@ -3204,7 +3275,6 @@ fn zirFunc( return sema.funcCommon( block, inst_data.src_node, - param_types, body_inst, extra.data.return_type, cc, @@ -3214,7 +3284,6 @@ fn zirFunc( false, src_locs, null, - &.{}, ); } @@ -3222,7 +3291,6 @@ fn funcCommon( sema: *Sema, block: *Scope.Block, src_node_offset: i32, - zir_param_types: []const Zir.Inst.Ref, body_inst: Zir.Inst.Index, zir_return_type: Zir.Inst.Ref, cc: std.builtin.CallingConvention, @@ -3232,7 +3300,6 @@ fn funcCommon( is_extern: bool, src_locs: Zir.Inst.Func.SrcLocs, opt_lib_name: ?[]const u8, - comptime_bits: []const u32, ) CompileError!Air.Inst.Ref { const src: LazySrcLoc = .{ .node_offset = src_node_offset }; const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset }; @@ -3245,7 +3312,7 @@ fn funcCommon( const fn_ty: Type = fn_ty: { // Hot path for some common function types. - if (zir_param_types.len == 0 and !var_args and align_val.tag() == .null_value and + if (sema.params.items.len == 0 and !var_args and align_val.tag() == .null_value and !inferred_error_set) { if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) { @@ -3266,22 +3333,21 @@ fn funcCommon( } var any_are_comptime = false; - const param_types = try sema.arena.alloc(Type, zir_param_types.len); - for (zir_param_types) |param_type, i| { - // TODO make a compile error from `resolveType` report the source location - // of the specific parameter. Will need to take a similar strategy as - // `resolveSwitchItemVal` to avoid resolving the source location unless - // we actually need to report an error. - const param_src = src; - param_types[i] = try sema.resolveType(block, param_src, param_type); - - any_are_comptime = any_are_comptime or blk: { - if (comptime_bits.len == 0) - break :blk false; - const bag = comptime_bits[i / 32]; - const is_comptime = @truncate(u1, bag >> @intCast(u5, i % 32)) != 0; - break :blk is_comptime; - }; + const param_types = try sema.arena.alloc(Type, sema.params.items.len); + const comptime_params = try sema.arena.alloc(bool, sema.params.items.len); + for (sema.params.items) |param, i| { + if (param.ty == .none) { + param_types[i] = Type.initTag(.noreturn); // indicates anytype + } else { + // TODO make a compile error from `resolveType` report the source location + // of the specific parameter. Will need to take a similar strategy as + // `resolveSwitchItemVal` to avoid resolving the source location unless + // we actually need to report an error. + const param_src = src; + param_types[i] = try sema.resolveType(block, param_src, param.ty); + } + comptime_params[i] = param.is_comptime; + any_are_comptime = any_are_comptime or param.is_comptime; } if (align_val.tag() != .null_value) { @@ -3301,6 +3367,7 @@ fn funcCommon( break :fn_ty try Type.Tag.function.create(sema.arena, .{ .param_types = param_types, + .comptime_params = comptime_params.ptr, .return_type = return_type, .cc = cc, .is_var_args = var_args, @@ -6545,16 +6612,6 @@ fn zirFuncExtended( break :blk align_tv.val; } else Value.initTag(.null_value); - const comptime_bits: []const u32 = if (!small.has_comptime_bits) &.{} else blk: { - const amt = (extra.data.param_types_len + 31) / 32; - const bit_bags = sema.code.extra[extra_index..][0..amt]; - extra_index += amt; - break :blk bit_bags; - }; - - const param_types = sema.code.refSlice(extra_index, extra.data.param_types_len); - extra_index += param_types.len; - var body_inst: Zir.Inst.Index = 0; var src_locs: Zir.Inst.Func.SrcLocs = undefined; if (extra.data.body_len != 0) { @@ -6570,7 +6627,6 @@ fn zirFuncExtended( return sema.funcCommon( block, extra.data.src_node, - param_types, body_inst, extra.data.return_type, cc, @@ -6580,7 +6636,6 @@ fn zirFuncExtended( is_extern, src_locs, lib_name, - comptime_bits, ); } diff --git a/src/Zir.zig b/src/Zir.zig index 109ae3b186..6445d73af5 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -173,11 +173,22 @@ pub const Inst = struct { /// Twos complement wrapping integer addition. /// Uses the `pl_node` union field. Payload is `Bin`. addwrap, - /// Declares a parameter of the current function. Used for debug info and - /// for checking shadowing against declarations in the current namespace. - /// Uses the `str_tok` field. Token is the parameter name, string is the - /// parameter name. - arg, + /// Declares a parameter of the current function. Used for: + /// * debug info + /// * checking shadowing against declarations in the current namespace + /// * parameter type expressions referencing other parameters + /// These occur in the block outside a function body (the same block as + /// contains the func instruction). + /// Uses the `pl_tok` field. Token is the parameter name, payload is a `Param`. + param, + /// Same as `param` except the parameter is marked comptime. + param_comptime, + /// Same as `param` except the parameter is marked anytype. + /// Uses the `str_tok` field. Token is the parameter name. String is the parameter name. + param_anytype, + /// Same as `param` except the parameter is marked both comptime and anytype. + /// Uses the `str_tok` field. Token is the parameter name. String is the parameter name. + param_anytype_comptime, /// Array concatenation. `a ++ b` /// Uses the `pl_node` union field. Payload is `Bin`. array_cat, @@ -971,7 +982,10 @@ pub const Inst = struct { /// Function calls do not count. pub fn isNoReturn(tag: Tag) bool { return switch (tag) { - .arg, + .param, + .param_comptime, + .param_anytype, + .param_anytype_comptime, .add, .addwrap, .alloc, @@ -1233,7 +1247,10 @@ pub const Inst = struct { break :list std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{ .add = .pl_node, .addwrap = .pl_node, - .arg = .str_tok, + .param = .pl_tok, + .param_comptime = .pl_tok, + .param_anytype = .str_tok, + .param_anytype_comptime = .str_tok, .array_cat = .pl_node, .array_mul = .pl_node, .array_type = .bin, @@ -2047,6 +2064,17 @@ pub const Inst = struct { return .{ .node_offset = self.src_node }; } }, + pl_tok: struct { + /// Offset from Decl AST token index. + src_tok: ast.TokenIndex, + /// index into extra. + /// `Tag` determines what lives there. + payload_index: u32, + + pub fn src(self: @This()) LazySrcLoc { + return .{ .token_offset = self.src_tok }; + } + }, bin: Bin, /// For strings which may contain null bytes. str: struct { @@ -2170,6 +2198,7 @@ pub const Inst = struct { un_node, un_tok, pl_node, + pl_tok, bin, str, str_tok, @@ -2226,17 +2255,11 @@ pub const Inst = struct { /// 0. lib_name: u32, // null terminated string index, if has_lib_name is set /// 1. cc: Ref, // if has_cc is set /// 2. align: Ref, // if has_align is set - /// 3. comptime_bits: u32 // for every 32 parameters, if has_comptime_bits is set - /// - sets of 1 bit: - /// 0bX: whether corresponding parameter is comptime - /// 4. param_type: Ref // for each param_types_len - /// - `none` indicates that the param type is `anytype`. - /// 5. body: Index // for each body_len - /// 6. src_locs: Func.SrcLocs // if body_len != 0 + /// 3. body: Index // for each body_len + /// 4. src_locs: Func.SrcLocs // if body_len != 0 pub const ExtendedFunc = struct { src_node: i32, return_type: Ref, - param_types_len: u32, body_len: u32, pub const Small = packed struct { @@ -2247,8 +2270,7 @@ pub const Inst = struct { has_align: bool, is_test: bool, is_extern: bool, - has_comptime_bits: bool, - _: u8 = undefined, + _: u9 = undefined, }; }; @@ -2271,13 +2293,10 @@ pub const Inst = struct { }; /// Trailing: - /// 0. param_type: Ref // for each param_types_len - /// - `none` indicates that the param type is `anytype`. - /// 1. body: Index // for each body_len - /// 2. src_locs: SrcLocs // if body_len != 0 + /// 0. body: Index // for each body_len + /// 1. src_locs: SrcLocs // if body_len != 0 pub const Func = struct { return_type: Ref, - param_types_len: u32, body_len: u32, pub const SrcLocs = struct { @@ -2764,6 +2783,12 @@ pub const Inst = struct { args: Ref, }; + pub const Param = struct { + /// Null-terminated string index. + name: u32, + ty: Ref, + }; + /// Trailing: /// 0. type_inst: Ref, // if small 0b000X is set /// 1. align_inst: Ref, // if small 0b00X0 is set @@ -3108,11 +3133,14 @@ const Writer = struct { .decl_ref, .decl_val, .import, - .arg, .ret_err_value, .ret_err_value_code, + .param_anytype, + .param_anytype_comptime, => try self.writeStrTok(stream, inst), + .param, .param_comptime => try self.writeParam(stream, inst), + .func => try self.writeFunc(stream, inst, false), .func_inferred => try self.writeFunc(stream, inst, true), @@ -3314,6 +3342,17 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writeParam(self: *Writer, stream: anytype, inst: Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_tok; + const extra = self.code.extraData(Inst.Param, inst_data.payload_index).data; + try stream.print("\"{}\", ", .{ + std.zig.fmtEscapes(self.code.nullTerminatedString(extra.name)), + }); + try self.writeInstRef(stream, extra.ty); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writePlNodeBin(self: *Writer, stream: anytype, inst: Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Inst.Bin, inst_data.payload_index).data; @@ -4277,16 +4316,14 @@ const Writer = struct { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const extra = self.code.extraData(Inst.Func, inst_data.payload_index); - const param_types = self.code.refSlice(extra.end, extra.data.param_types_len); - const body = self.code.extra[extra.end + param_types.len ..][0..extra.data.body_len]; + const body = self.code.extra[extra.end..][0..extra.data.body_len]; var src_locs: Zir.Inst.Func.SrcLocs = undefined; if (body.len != 0) { - const extra_index = extra.end + param_types.len + body.len; + const extra_index = extra.end + body.len; src_locs = self.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } return self.writeFuncCommon( stream, - param_types, extra.data.return_type, inferred_error_set, false, @@ -4296,7 +4333,6 @@ const Writer = struct { body, src, src_locs, - &.{}, ); } @@ -4323,16 +4359,6 @@ const Writer = struct { break :blk align_inst; }; - const comptime_bits: []const u32 = if (!small.has_comptime_bits) &.{} else blk: { - const amt = (extra.data.param_types_len + 31) / 32; - const bit_bags = self.code.extra[extra_index..][0..amt]; - extra_index += amt; - break :blk bit_bags; - }; - - const param_types = self.code.refSlice(extra_index, extra.data.param_types_len); - extra_index += param_types.len; - const body = self.code.extra[extra_index..][0..extra.data.body_len]; extra_index += body.len; @@ -4342,7 +4368,6 @@ const Writer = struct { } return self.writeFuncCommon( stream, - param_types, extra.data.return_type, small.is_inferred_error, small.is_var_args, @@ -4352,7 +4377,6 @@ const Writer = struct { body, src, src_locs, - comptime_bits, ); } @@ -4426,7 +4450,6 @@ const Writer = struct { fn writeFuncCommon( self: *Writer, stream: anytype, - param_types: []const Inst.Ref, ret_ty: Inst.Ref, inferred_error_set: bool, var_args: bool, @@ -4436,19 +4459,7 @@ const Writer = struct { body: []const Inst.Index, src: LazySrcLoc, src_locs: Zir.Inst.Func.SrcLocs, - comptime_bits: []const u32, ) !void { - try stream.writeAll("["); - for (param_types) |param_type, i| { - if (i != 0) try stream.writeAll(", "); - if (comptime_bits.len != 0) { - const bag = comptime_bits[i / 32]; - const is_comptime = @truncate(u1, bag >> @intCast(u5, i % 32)) != 0; - try self.writeFlag(stream, "comptime ", is_comptime); - } - try self.writeInstRef(stream, param_type); - } - try stream.writeAll("], "); try self.writeInstRef(stream, ret_ty); try self.writeOptionalInstRef(stream, ", cc=", cc); try self.writeOptionalInstRef(stream, ", align=", align_inst); @@ -4714,8 +4725,7 @@ fn findDeclsInner( const inst_data = datas[inst].pl_node; const extra = zir.extraData(Inst.Func, inst_data.payload_index); - const param_types_len = extra.data.param_types_len; - const body = zir.extra[extra.end + param_types_len ..][0..extra.data.body_len]; + const body = zir.extra[extra.end..][0..extra.data.body_len]; return zir.findDeclsBody(list, body); }, .extended => { @@ -4730,7 +4740,6 @@ fn findDeclsInner( extra_index += @boolToInt(small.has_lib_name); extra_index += @boolToInt(small.has_cc); extra_index += @boolToInt(small.has_align); - extra_index += extra.data.param_types_len; const body = zir.extra[extra_index..][0..extra.data.body_len]; return zir.findDeclsBody(list, body); }, diff --git a/src/type.zig b/src/type.zig index a8c3d77bbb..feb16fd47c 100644 --- a/src/type.zig +++ b/src/type.zig @@ -759,12 +759,15 @@ pub const Type = extern union { for (payload.param_types) |param_type, i| { param_types[i] = try param_type.copy(allocator); } + const other_comptime_params = payload.comptime_params[0..payload.param_types.len]; + const comptime_params = try allocator.dupe(bool, other_comptime_params); return Tag.function.create(allocator, .{ .return_type = try payload.return_type.copy(allocator), .param_types = param_types, .cc = payload.cc, .is_var_args = payload.is_var_args, .is_generic = payload.is_generic, + .comptime_params = comptime_params.ptr, }); }, .pointer => { @@ -2408,14 +2411,41 @@ pub const Type = extern union { }; } - /// Asserts the type is a function. - pub fn fnIsGeneric(self: Type) bool { - return switch (self.tag()) { - .fn_noreturn_no_args => false, - .fn_void_no_args => false, - .fn_naked_noreturn_no_args => false, - .fn_ccc_void_no_args => false, - .function => self.castTag(.function).?.data.is_generic, + pub fn fnInfo(ty: Type) Payload.Function.Data { + return switch (ty.tag()) { + .fn_noreturn_no_args => .{ + .param_types = &.{}, + .comptime_params = undefined, + .return_type = initTag(.noreturn), + .cc = .Unspecified, + .is_var_args = false, + .is_generic = false, + }, + .fn_void_no_args => .{ + .param_types = &.{}, + .comptime_params = undefined, + .return_type = initTag(.void), + .cc = .Unspecified, + .is_var_args = false, + .is_generic = false, + }, + .fn_naked_noreturn_no_args => .{ + .param_types = &.{}, + .comptime_params = undefined, + .return_type = initTag(.noreturn), + .cc = .Naked, + .is_var_args = false, + .is_generic = false, + }, + .fn_ccc_void_no_args => .{ + .param_types = &.{}, + .comptime_params = undefined, + .return_type = initTag(.void), + .cc = .C, + .is_var_args = false, + .is_generic = false, + }, + .function => ty.castTag(.function).?.data, else => unreachable, }; @@ -3223,13 +3253,23 @@ pub const Type = extern union { pub const base_tag = Tag.function; base: Payload = Payload{ .tag = base_tag }, - data: struct { + data: Data, + + // TODO look into optimizing this memory to take fewer bytes + const Data = struct { param_types: []Type, + comptime_params: [*]bool, return_type: Type, cc: std.builtin.CallingConvention, is_var_args: bool, is_generic: bool, - }, + + fn paramIsComptime(self: @This(), i: usize) bool { + if (!self.is_generic) return false; + assert(i < self.param_types.len); + return self.comptime_params[i]; + } + }; }; pub const ErrorSet = struct { -- cgit v1.2.3 From 609b84611dcde382af5d9fbc2345ede468d31a6f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Aug 2021 17:29:59 -0700 Subject: stage2: rework runtime, comptime, inline function calls * ZIR function instructions encode the index of the block that contains the function instruction. This allows Zig to later scan the block and find the parameter instructions, which is needed for semantically analyzing function bodies. * Runtime function calls insert AIR arg instructions and then inserts Sema inst_map entries mapping the ZIR param instructions to them. * comptime/inline function call inserts Sema inst_map entries mapping the ZIR param instructions to the AIR callsite arguments. With this commit we are back to the tests passing. --- BRANCH_TODO | 2 -- src/AstGen.zig | 7 ++++++ src/Module.zig | 57 +++++++++++++++++++++++++++++--------------- src/Sema.zig | 75 ++++++++++++++++------------------------------------------ src/Zir.zig | 54 +++++++++++++++++++++++++++++++++++++++--- 5 files changed, 117 insertions(+), 78 deletions(-) (limited to 'src/Module.zig') diff --git a/BRANCH_TODO b/BRANCH_TODO index bc0a67f799..1cdd8362e7 100644 --- a/BRANCH_TODO +++ b/BRANCH_TODO @@ -1,6 +1,4 @@ * update arg instructions: - - runtime function call inserts AIR arg instructions and Sema map items for them - - comptime/inline function call inserts Sema map items for the args - generic instantiation inserts Sema map items for the comptime args only, re-runs the Decl ZIR to get the new Fn. * generic function call where it makes a new function diff --git a/src/AstGen.zig b/src/AstGen.zig index 0b78c839a0..f88b59d211 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1125,6 +1125,7 @@ fn fnProtoExpr( const result = try gz.addFunc(.{ .src_node = fn_proto.ast.proto_node, + .param_block = 0, .ret_ty = return_type_inst, .body = &[0]Zir.Inst.Index{}, .cc = cc, @@ -3035,6 +3036,7 @@ fn fnDecl( break :func try decl_gz.addFunc(.{ .src_node = decl_node, .ret_ty = return_type_inst, + .param_block = block_inst, .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = .none, // passed in the per-decl data @@ -3071,6 +3073,7 @@ fn fnDecl( break :func try decl_gz.addFunc(.{ .src_node = decl_node, + .param_block = block_inst, .ret_ty = return_type_inst, .body = fn_gz.instructions.items, .cc = cc, @@ -3415,6 +3418,7 @@ fn testDecl( const func_inst = try decl_block.addFunc(.{ .src_node = node, + .param_block = block_inst, .ret_ty = .void_type, .body = fn_block.instructions.items, .cc = .none, @@ -9111,6 +9115,7 @@ const GenZir = struct { fn addFunc(gz: *GenZir, args: struct { src_node: ast.Node.Index, body: []const Zir.Inst.Index, + param_block: Zir.Inst.Index, ret_ty: Zir.Inst.Ref, cc: Zir.Inst.Ref, align_inst: Zir.Inst.Ref, @@ -9170,6 +9175,7 @@ const GenZir = struct { ); const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{ .src_node = gz.nodeIndexToRelative(args.src_node), + .param_block = args.param_block, .return_type = args.ret_ty, .body_len = @intCast(u32, args.body.len), }); @@ -9212,6 +9218,7 @@ const GenZir = struct { ); const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{ + .param_block = args.param_block, .return_type = args.ret_ty, .body_len = @intCast(u32, args.body.len), }); diff --git a/src/Module.zig b/src/Module.zig index fa8b4ca768..6253e2808d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2899,7 +2899,6 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { .namespace = &struct_obj.namespace, .func = null, .owner_func = null, - .param_inst_list = &.{}, }; defer sema.deinit(); var block_scope: Scope.Block = .{ @@ -2954,7 +2953,6 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { .namespace = decl.namespace, .func = null, .owner_func = null, - .param_inst_list = &.{}, }; defer sema.deinit(); @@ -3625,8 +3623,6 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { defer decl.value_arena.?.* = arena.state; const fn_ty = decl.ty; - const param_inst_list = try gpa.alloc(Air.Inst.Ref, fn_ty.fnParamLen()); - defer gpa.free(param_inst_list); var sema: Sema = .{ .mod = mod, @@ -3637,7 +3633,6 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { .namespace = decl.namespace, .func = func, .owner_func = func, - .param_inst_list = param_inst_list, }; defer sema.deinit(); @@ -3656,29 +3651,55 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { }; defer inner_block.instructions.deinit(gpa); - // AIR requires the arg parameters to be the first N instructions. - try inner_block.instructions.ensureTotalCapacity(gpa, param_inst_list.len); - for (param_inst_list) |*param_inst, param_index| { + const fn_info = sema.code.getFnInfo(func.zir_body_inst); + const zir_tags = sema.code.instructions.items(.tag); + + // Here we are performing "runtime semantic analysis" for a function body, which means + // we must map the parameter ZIR instructions to `arg` AIR instructions. + // AIR requires the `arg` parameters to be the first N instructions. + const params_len = @intCast(u32, fn_ty.fnParamLen()); + try inner_block.instructions.ensureTotalCapacity(gpa, params_len); + try sema.air_instructions.ensureUnusedCapacity(gpa, params_len * 2); // * 2 for the `addType` + try sema.inst_map.ensureUnusedCapacity(gpa, params_len); + + var param_index: usize = 0; + for (fn_info.param_body) |inst| { + const name = switch (zir_tags[inst]) { + .param, .param_comptime => blk: { + const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; + const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; + break :blk extra.name; + }, + + .param_anytype, .param_anytype_comptime => blk: { + const str_tok = sema.code.instructions.items(.data)[inst].str_tok; + break :blk str_tok.start; + }, + + else => continue, + }; const param_type = fn_ty.fnParamType(param_index); + param_index += 1; const ty_ref = try sema.addType(param_type); const arg_index = @intCast(u32, sema.air_instructions.len); inner_block.instructions.appendAssumeCapacity(arg_index); - param_inst.* = Air.indexToRef(arg_index); - try sema.air_instructions.append(gpa, .{ + sema.air_instructions.appendAssumeCapacity(.{ .tag = .arg, - .data = .{ - .ty_str = .{ - .ty = ty_ref, - .str = undefined, // Set in the semantic analysis of the arg instruction. - }, - }, + .data = .{ .ty_str = .{ + .ty = ty_ref, + .str = name, + } }, }); + sema.inst_map.putAssumeCapacityNoClobber(inst, Air.indexToRef(arg_index)); } func.state = .in_progress; log.debug("set {s} to in_progress", .{decl.name}); - try sema.analyzeFnBody(&inner_block, func.zir_body_inst); + _ = sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) { + error.NeededSourceLocation => unreachable, + else => |e| return e, + }; // Copy the block into place and mark that as the main block. try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len + @@ -4330,7 +4351,6 @@ pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void .namespace = &struct_obj.namespace, .owner_func = null, .func = null, - .param_inst_list = &.{}, }; defer sema.deinit(); @@ -4484,7 +4504,6 @@ pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void { .namespace = &union_obj.namespace, .owner_func = null, .func = null, - .param_inst_list = &.{}, }; defer sema.deinit(); diff --git a/src/Sema.zig b/src/Sema.zig index 5fd3c149a2..923295069d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -29,13 +29,6 @@ owner_func: ?*Module.Fn, /// This starts out the same as `owner_func` and then diverges in the case of /// an inline or comptime function call. func: ?*Module.Fn, -/// For now, AIR requires arg instructions to be the first N instructions in the -/// AIR code. We store references here for the purpose of `resolveInst`. -/// This can get reworked with AIR memory layout changes, into simply: -/// > Denormalized data to make `resolveInst` faster. This is 0 if not inside a function, -/// > otherwise it is the number of parameters of the function. -/// > param_count: u32 -param_inst_list: []const Air.Inst.Ref, branch_quota: u32 = 1000, branch_count: u32 = 0, /// This field is updated when a new source location becomes active, so that @@ -85,43 +78,10 @@ pub fn deinit(sema: *Sema) void { sema.air_values.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); + sema.params.deinit(gpa); sema.* = undefined; } -pub fn analyzeFnBody( - sema: *Sema, - block: *Scope.Block, - fn_body_inst: Zir.Inst.Index, -) SemaError!void { - const tags = sema.code.instructions.items(.tag); - const datas = sema.code.instructions.items(.data); - const body: []const Zir.Inst.Index = switch (tags[fn_body_inst]) { - .func, .func_inferred => blk: { - const inst_data = datas[fn_body_inst].pl_node; - const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index); - const body = sema.code.extra[extra.end..][0..extra.data.body_len]; - break :blk body; - }, - .extended => blk: { - const extended = datas[fn_body_inst].extended; - assert(extended.opcode == .func); - const extra = sema.code.extraData(Zir.Inst.ExtendedFunc, extended.operand); - const small = @bitCast(Zir.Inst.ExtendedFunc.Small, extended.small); - var extra_index: usize = extra.end; - extra_index += @boolToInt(small.has_lib_name); - extra_index += @boolToInt(small.has_cc); - extra_index += @boolToInt(small.has_align); - const body = sema.code.extra[extra_index..][0..extra.data.body_len]; - break :blk body; - }, - else => unreachable, - }; - _ = sema.analyzeBody(block, body) catch |err| switch (err) { - error.NeededSourceLocation => unreachable, - else => |e| return e, - }; -} - /// Returns only the result from the body that is specified. /// Only appropriate to call when it is determined at comptime that this body /// has no peers. @@ -1066,7 +1026,6 @@ fn zirEnumDecl( .namespace = &enum_obj.namespace, .owner_func = null, .func = null, - .param_inst_list = &.{}, .branch_quota = sema.branch_quota, .branch_count = sema.branch_count, }; @@ -2538,10 +2497,6 @@ fn analyzeCall( sema.func = module_fn; defer sema.func = parent_func; - const parent_param_inst_list = sema.param_inst_list; - sema.param_inst_list = args; - defer sema.param_inst_list = parent_param_inst_list; - const parent_next_arg_index = sema.next_arg_index; sema.next_arg_index = 0; defer sema.next_arg_index = parent_next_arg_index; @@ -2565,12 +2520,23 @@ fn analyzeCall( try sema.emitBackwardBranch(&child_block, call_src); // This will have return instructions analyzed as break instructions to - // the block_inst above. - try sema.analyzeFnBody(&child_block, module_fn.zir_body_inst); - - const result = try sema.analyzeBlockBody(block, call_src, &child_block, merges); - - break :res result; + // the block_inst above. Here we are performing "comptime/inline semantic analysis" + // for a function body, which means we must map the parameter ZIR instructions to + // the AIR instructions of the callsite. + const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); + const zir_tags = sema.code.instructions.items(.tag); + var arg_i: usize = 0; + try sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, args.len)); + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param, .param_comptime, .param_anytype, .param_anytype_comptime => {}, + else => continue, + } + sema.inst_map.putAssumeCapacityNoClobber(inst, args[arg_i]); + arg_i += 1; + } + _ = try sema.analyzeBody(&child_block, fn_info.body); + break :res try sema.analyzeBlockBody(block, call_src, &child_block, merges); } else if (func_ty_info.is_generic) { const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = func_val.castTag(.function).?.data; @@ -2601,7 +2567,7 @@ fn analyzeCall( // TODO // Queue up a `codegen_func` work item for the new Fn, making sure it will have - // `analyzeFnBody` called with the Scope which contains the comptime parameters. + // `analyzeBody` called with the ZIR parameters mapped appropriately. // TODO // Save it into the Module's generic function map. @@ -3344,11 +3310,12 @@ fn funcCommon( // `resolveSwitchItemVal` to avoid resolving the source location unless // we actually need to report an error. const param_src = src; - param_types[i] = try sema.resolveType(block, param_src, param.ty); + param_types[i] = try sema.analyzeAsType(block, param_src, param.ty); } comptime_params[i] = param.is_comptime; any_are_comptime = any_are_comptime or param.is_comptime; } + sema.params.clearRetainingCapacity(); if (align_val.tag() != .null_value) { return mod.fail(&block.base, src, "TODO implement support for function prototypes to have alignment specified", .{}); diff --git a/src/Zir.zig b/src/Zir.zig index 6445d73af5..b7f4c28161 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -61,7 +61,7 @@ pub const ExtraIndex = enum(u32) { _, }; -pub fn getMainStruct(zir: Zir) Zir.Inst.Index { +pub fn getMainStruct(zir: Zir) Inst.Index { return zir.extra[@enumToInt(ExtraIndex.main_struct)] - @intCast(u32, Inst.Ref.typed_value_map.len); } @@ -2260,6 +2260,8 @@ pub const Inst = struct { pub const ExtendedFunc = struct { src_node: i32, return_type: Ref, + /// Points to the block that contains the param instructions for this function. + param_block: Index, body_len: u32, pub const Small = packed struct { @@ -2297,6 +2299,8 @@ pub const Inst = struct { /// 1. src_locs: SrcLocs // if body_len != 0 pub const Func = struct { return_type: Ref, + /// Points to the block that contains the param instructions for this function. + param_block: Index, body_len: u32, pub const SrcLocs = struct { @@ -4894,10 +4898,54 @@ fn findDeclsSwitchMulti( fn findDeclsBody( zir: Zir, - list: *std.ArrayList(Zir.Inst.Index), - body: []const Zir.Inst.Index, + list: *std.ArrayList(Inst.Index), + body: []const Inst.Index, ) Allocator.Error!void { for (body) |member| { try zir.findDeclsInner(list, member); } } + +pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) struct { + param_body: []const Inst.Index, + body: []const Inst.Index, +} { + const tags = zir.instructions.items(.tag); + const datas = zir.instructions.items(.data); + const info: struct { + param_block: Inst.Index, + body: []const Inst.Index, + } = switch (tags[fn_inst]) { + .func, .func_inferred => blk: { + const inst_data = datas[fn_inst].pl_node; + const extra = zir.extraData(Inst.Func, inst_data.payload_index); + const body = zir.extra[extra.end..][0..extra.data.body_len]; + break :blk .{ + .param_block = extra.data.param_block, + .body = body, + }; + }, + .extended => blk: { + const extended = datas[fn_inst].extended; + assert(extended.opcode == .func); + const extra = zir.extraData(Inst.ExtendedFunc, extended.operand); + const small = @bitCast(Inst.ExtendedFunc.Small, extended.small); + var extra_index: usize = extra.end; + extra_index += @boolToInt(small.has_lib_name); + extra_index += @boolToInt(small.has_cc); + extra_index += @boolToInt(small.has_align); + const body = zir.extra[extra_index..][0..extra.data.body_len]; + break :blk .{ + .param_block = extra.data.param_block, + .body = body, + }; + }, + else => unreachable, + }; + assert(tags[info.param_block] == .block or tags[info.param_block] == .block_inline); + const param_block = zir.extraData(Inst.Block, datas[info.param_block].pl_node.payload_index); + return .{ + .param_body = zir.extra[param_block.end..][0..param_block.data.body_len], + .body = info.body, + }; +} -- cgit v1.2.3 From 382d201781eb57d9e950ad07ce814adc5a68b329 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Aug 2021 22:34:22 -0700 Subject: stage2: basic generic functions are working The general strategy is that Sema will pre-map comptime arguments into the inst_map, and then re-run the block body that contains the `param` and `func` instructions. This re-runs all the parameter type expressions except with comptime values populated. In Sema, param instructions are now handled specially: they detect whether they are comptime-elided or not. If so, they skip putting a value in the inst_map, since it is already pre-populated. If not, then they append to the `fields` field of `Sema` for use with the `func` instruction. So when the block body is re-run, a new function is generated with all the comptime arguments elided, and the new function type has only runtime parameters in it. TODO: give the generated Decls better names than "foo__anon_x". The new function is then added to the work queue to have its body analyzed and a runtime call AIR instruction to the new function is emitted. When the new function gets semantically analyzed, comptime parameters are pre-mapped to the corresponding `comptime_args` values rather than mapped to an `arg` AIR instruction. `comptime_args` is a new field that `Fn` has which is a `TypedValue` for each parameter. This field is non-null for generic function instantiations only. The values are the comptime arguments. For non-comptime parameters, a sentinel value is used. This is because we need to know the information of which parameters are comptime-known. Additionally: * AstGen: align and section expressions are evaluated in the scope that has comptime parameters in it. There are still some TODO items left; see the BRANCH_TODO file. --- BRANCH_TODO | 7 +- src/AstGen.zig | 16 +-- src/Module.zig | 27 ++++- src/Sema.zig | 346 +++++++++++++++++++++++++++++++++++++++------------------ src/Zir.zig | 14 ++- src/type.zig | 3 +- 6 files changed, 287 insertions(+), 126 deletions(-) (limited to 'src/Module.zig') diff --git a/BRANCH_TODO b/BRANCH_TODO index 1cdd8362e7..e8606332d7 100644 --- a/BRANCH_TODO +++ b/BRANCH_TODO @@ -1,7 +1,4 @@ -* update arg instructions: - - generic instantiation inserts Sema map items for the comptime args only, re-runs the - Decl ZIR to get the new Fn. -* generic function call where it makes a new function * memoize the instantiation in a table -* anytype with next parameter expression using it +* expressions that depend on comptime stuff need a poison value to use for + types when generating the generic function type * comptime anytype diff --git a/src/AstGen.zig b/src/AstGen.zig index f88b59d211..7534afe961 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2906,14 +2906,7 @@ fn fnDecl( const maybe_inline_token = fn_proto.extern_export_inline_token orelse break :blk false; break :blk token_tags[maybe_inline_token] == .keyword_inline; }; - const align_inst: Zir.Inst.Ref = if (fn_proto.ast.align_expr == 0) .none else inst: { - break :inst try expr(&decl_gz, &decl_gz.base, align_rl, fn_proto.ast.align_expr); - }; - const section_inst: Zir.Inst.Ref = if (fn_proto.ast.section_expr == 0) .none else inst: { - break :inst try comptimeExpr(&decl_gz, &decl_gz.base, .{ .ty = .const_slice_u8_type }, fn_proto.ast.section_expr); - }; - - try wip_decls.next(gpa, is_pub, is_export, align_inst != .none, section_inst != .none); + try wip_decls.next(gpa, is_pub, is_export, fn_proto.ast.align_expr != 0, fn_proto.ast.section_expr != 0); var params_scope = &fn_gz.base; const is_var_args = is_var_args: { @@ -2994,6 +2987,13 @@ fn fnDecl( const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1; const is_inferred_error = token_tags[maybe_bang] == .bang; + const align_inst: Zir.Inst.Ref = if (fn_proto.ast.align_expr == 0) .none else inst: { + break :inst try expr(&decl_gz, params_scope, align_rl, fn_proto.ast.align_expr); + }; + const section_inst: Zir.Inst.Ref = if (fn_proto.ast.section_expr == 0) .none else inst: { + break :inst try comptimeExpr(&decl_gz, params_scope, .{ .ty = .const_slice_u8_type }, fn_proto.ast.section_expr); + }; + const return_type_inst = try AstGen.expr( &decl_gz, params_scope, diff --git a/src/Module.zig b/src/Module.zig index 6253e2808d..184ea617b1 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -757,6 +757,10 @@ pub const Union = struct { pub const Fn = struct { /// The Decl that corresponds to the function itself. owner_decl: *Decl, + /// If this is not null, this function is a generic function instantiation, and + /// there is a `Value` here for each parameter of the function. Non-comptime + /// parameters are marked with an `unreachable_value`. + comptime_args: ?[*]TypedValue = null, /// The ZIR instruction that is a function instruction. Use this to find /// the body. We store this rather than the body directly so that when ZIR /// is regenerated on update(), we can map this to the new corresponding @@ -3657,10 +3661,13 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { // Here we are performing "runtime semantic analysis" for a function body, which means // we must map the parameter ZIR instructions to `arg` AIR instructions. // AIR requires the `arg` parameters to be the first N instructions. - const params_len = @intCast(u32, fn_ty.fnParamLen()); - try inner_block.instructions.ensureTotalCapacity(gpa, params_len); - try sema.air_instructions.ensureUnusedCapacity(gpa, params_len * 2); // * 2 for the `addType` - try sema.inst_map.ensureUnusedCapacity(gpa, params_len); + // This could be a generic function instantiation, however, in which case we need to + // map the comptime parameters to constant values and only emit arg AIR instructions + // for the runtime ones. + const runtime_params_len = @intCast(u32, fn_ty.fnParamLen()); + try inner_block.instructions.ensureTotalCapacity(gpa, runtime_params_len); + try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType` + try sema.inst_map.ensureUnusedCapacity(gpa, fn_info.total_params_len); var param_index: usize = 0; for (fn_info.param_body) |inst| { @@ -3678,8 +3685,17 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { else => continue, }; + if (func.comptime_args) |comptime_args| { + const arg_tv = comptime_args[param_index]; + if (arg_tv.val.tag() != .unreachable_value) { + // We have a comptime value for this parameter. + const arg = try sema.addConstant(arg_tv.ty, arg_tv.val); + sema.inst_map.putAssumeCapacityNoClobber(inst, arg); + param_index += 1; + continue; + } + } const param_type = fn_ty.fnParamType(param_index); - param_index += 1; const ty_ref = try sema.addType(param_type); const arg_index = @intCast(u32, sema.air_instructions.len); inner_block.instructions.appendAssumeCapacity(arg_index); @@ -3691,6 +3707,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { } }, }); sema.inst_map.putAssumeCapacityNoClobber(inst, Air.indexToRef(arg_index)); + param_index += 1; } func.state = .in_progress; diff --git a/src/Sema.zig b/src/Sema.zig index 923295069d..9a7eb6fc40 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -36,9 +36,14 @@ branch_count: u32 = 0, /// access to the source location set by the previous instruction which did /// contain a mapped source location. src: LazySrcLoc = .{ .token_offset = 0 }, -next_arg_index: usize = 0, -params: std.ArrayListUnmanaged(Param) = .{}, decl_val_table: std.AutoHashMapUnmanaged(*Decl, Air.Inst.Ref) = .{}, +/// `param` instructions are collected here to be used by the `func` instruction. +params: std.ArrayListUnmanaged(Param) = .{}, +/// When doing a generic function instantiation, this array collects a `Value` object for +/// each parameter that is comptime known and thus elided from the generated function. +/// This memory is allocated by a parent `Sema` and owned by the values arena of the owner_decl. +comptime_args: []TypedValue = &.{}, +next_arg_index: usize = 0, const std = @import("std"); const mem = std.mem; @@ -64,8 +69,8 @@ const target_util = @import("target.zig"); const Param = struct { name: [:0]const u8, - /// `none` means `anytype`. - ty: Air.Inst.Ref, + /// `noreturn` means `anytype`. + ty: Type, is_comptime: bool, }; @@ -366,26 +371,6 @@ pub fn analyzeBody( // continue the loop. // We also know that they cannot be referenced later, so we avoid // putting them into the map. - .param => { - try sema.zirParam(inst, false); - i += 1; - continue; - }, - .param_comptime => { - try sema.zirParam(inst, true); - i += 1; - continue; - }, - .param_anytype => { - try sema.zirParamAnytype(inst, false); - i += 1; - continue; - }, - .param_anytype_comptime => { - try sema.zirParamAnytype(inst, true); - i += 1; - continue; - }, .breakpoint => { try sema.zirBreakpoint(block, inst); i += 1; @@ -519,6 +504,88 @@ pub fn analyzeBody( return break_inst; } }, + .param => blk: { + const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; + const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; + const param_name = sema.code.nullTerminatedString(extra.name); + + if (sema.nextArgIsComptimeElided()) { + i += 1; + continue; + } + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + + const param_ty = try sema.resolveType(block, src, extra.ty); + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = param_ty, + .is_comptime = false, + }); + break :blk try sema.addConstUndef(param_ty); + }, + .param_comptime => blk: { + const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; + const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; + const param_name = sema.code.nullTerminatedString(extra.name); + + if (sema.nextArgIsComptimeElided()) { + i += 1; + continue; + } + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + + const param_ty = try sema.resolveType(block, src, extra.ty); + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = param_ty, + .is_comptime = true, + }); + break :blk try sema.addConstUndef(param_ty); + }, + .param_anytype => blk: { + const inst_data = sema.code.instructions.items(.data)[inst].str_tok; + const param_name = inst_data.get(sema.code); + + if (sema.nextArgIsComptimeElided()) { + i += 1; + continue; + } + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = Type.initTag(.noreturn), + .is_comptime = false, + }); + break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); + }, + .param_anytype_comptime => blk: { + const inst_data = sema.code.instructions.items(.data)[inst].str_tok; + const param_name = inst_data.get(sema.code); + + if (sema.nextArgIsComptimeElided()) { + i += 1; + continue; + } + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + + try sema.params.append(sema.gpa, .{ + .name = param_name, + .ty = Type.initTag(.noreturn), + .is_comptime = true, + }); + break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); + }, }; if (sema.typeOf(air_inst).isNoReturn()) return always_noreturn; @@ -1339,36 +1406,6 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co return sema.analyzeLoad(block, src, result_ptr, result_ptr_src); } -fn zirParam(sema: *Sema, inst: Zir.Inst.Index, is_comptime: bool) CompileError!void { - const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; - const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; - const param_name = sema.code.nullTerminatedString(extra.name); - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - const param_ty = sema.resolveInst(extra.ty); - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = param_ty, - .is_comptime = is_comptime, - }); -} - -fn zirParamAnytype(sema: *Sema, inst: Zir.Inst.Index, is_comptime: bool) CompileError!void { - const inst_data = sema.code.instructions.items(.data)[inst].str_tok; - const param_name = inst_data.get(sema.code); - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = .none, - .is_comptime = is_comptime, - }); -} - fn zirAllocExtended( sema: *Sema, block: *Scope.Block, @@ -2497,10 +2534,6 @@ fn analyzeCall( sema.func = module_fn; defer sema.func = parent_func; - const parent_next_arg_index = sema.next_arg_index; - sema.next_arg_index = 0; - defer sema.next_arg_index = parent_next_arg_index; - var child_block: Scope.Block = .{ .parent = null, .sema = sema, @@ -2537,7 +2570,7 @@ fn analyzeCall( } _ = try sema.analyzeBody(&child_block, fn_info.body); break :res try sema.analyzeBlockBody(block, call_src, &child_block, merges); - } else if (func_ty_info.is_generic) { + } else if (func_ty_info.is_generic) res: { const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = func_val.castTag(.function).?.data; // Check the Module's generic function map with an adapted context, so that we @@ -2545,37 +2578,142 @@ fn analyzeCall( // only to junk it if it matches an existing instantiation. // TODO - // Create a Decl for the new function. - const generic_namespace = try sema.arena.create(Module.Scope.Namespace); - generic_namespace.* = .{ - .parent = block.src_decl.namespace, - .file_scope = block.src_decl.namespace.file_scope, - .ty = func_ty, + const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); + const zir_tags = sema.code.instructions.items(.tag); + var non_comptime_args_len: u32 = 0; + const new_func = new_func: { + const namespace = module_fn.owner_decl.namespace; + try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); + + // Create a Decl for the new function. + const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node); + // TODO better names for generic function instantiations + const name_index = mod.getNextAnonNameIndex(); + new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ + module_fn.owner_decl.name, name_index, + }); + new_decl.src_line = module_fn.owner_decl.src_line; + new_decl.is_pub = module_fn.owner_decl.is_pub; + new_decl.is_exported = module_fn.owner_decl.is_exported; + new_decl.has_align = module_fn.owner_decl.has_align; + new_decl.has_linksection = module_fn.owner_decl.has_linksection; + new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; + new_decl.alive = true; // This Decl is called at runtime. + new_decl.has_tv = true; + new_decl.owns_tv = true; + new_decl.analysis = .in_progress; + new_decl.generation = mod.generation; + + namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + + // Re-run the block that creates the function, with the comptime parameters + // pre-populated inside `inst_map`. This causes `param_comptime` and + // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a + // new, monomorphized function, with the comptime parameters elided. + var child_sema: Sema = .{ + .mod = mod, + .gpa = gpa, + .arena = sema.arena, + .code = sema.code, + .owner_decl = new_decl, + .namespace = namespace, + .func = null, + .owner_func = null, + .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, args.len), + }; + defer child_sema.deinit(); + + var child_block: Scope.Block = .{ + .parent = null, + .sema = &child_sema, + .src_decl = new_decl, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + }; + defer child_block.instructions.deinit(gpa); + + try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, args.len)); + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + const is_comptime = switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime => true, + .param, .param_anytype => false, // TODO make true for always comptime types + else => continue, + }; + if (is_comptime) { + // TODO: pass .unneeded to resolveConstValue and then if we get + // error.NeededSourceLocation resolve the arg source location and + // try again. + const arg_src = call_src; + const arg = args[arg_i]; + const arg_val = try sema.resolveConstValue(block, arg_src, arg); + child_sema.comptime_args[arg_i] = .{ + .ty = try sema.typeOf(arg).copy(&new_decl_arena.allocator), + .val = try arg_val.copy(&new_decl_arena.allocator), + }; + const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else { + non_comptime_args_len += 1; + child_sema.comptime_args[arg_i] = .{ + .ty = Type.initTag(.noreturn), + .val = Value.initTag(.unreachable_value), + }; + } + arg_i += 1; + } + const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); + const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); + const new_func = new_func_val.castTag(.function).?.data; + + // Populate the Decl ty/val with the function and its type. + new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); + new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); + new_decl.analysis = .complete; + + // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field + // will be populated, ensuring it will have `analyzeBody` called with the ZIR + // parameters mapped appropriately. + try mod.comp.bin_file.allocateDeclIndexes(new_decl); + try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); + + try new_decl.finalizeNewArena(&new_decl_arena); + break :new_func try sema.analyzeDeclVal(block, func_src, new_decl); }; - const new_decl = try mod.allocateNewDecl(generic_namespace, module_fn.owner_decl.src_node); - _ = new_decl; - - // Iterate over the parameters that are comptime, evaluating their type expressions - // inside a Scope which contains the previous parameters. - //for (args) |arg, arg_i| { - //} - - // Create a new Fn with only the runtime-known parameters. - // TODO - - // Populate the Decl ty/val with the function and its type. - // TODO - - // Queue up a `codegen_func` work item for the new Fn, making sure it will have - // `analyzeBody` called with the ZIR parameters mapped appropriately. - // TODO // Save it into the Module's generic function map. // TODO - // Call it the same as a runtime function. - // TODO - return mod.fail(&block.base, func_src, "TODO implement generic fn call", .{}); + // Make a runtime call to the new function, making sure to omit the comptime args. + try sema.requireRuntimeBlock(block, call_src); + try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + + non_comptime_args_len); + const func_inst = try block.addInst(.{ + .tag = .call, + .data = .{ .pl_op = .{ + .operand = new_func, + .payload = sema.addExtraAssumeCapacity(Air.Call{ + .args_len = non_comptime_args_len, + }), + } }, + }); + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + const is_comptime = switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime => true, + .param, .param_anytype => false, // TODO make true for always comptime types + else => continue, + }; + if (is_comptime) { + sema.air_extra.appendAssumeCapacity(@enumToInt(args[arg_i])); + } + arg_i += 1; + } + break :res func_inst; } else res: { try sema.requireRuntimeBlock(block, call_src); try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + @@ -3302,15 +3440,10 @@ fn funcCommon( const param_types = try sema.arena.alloc(Type, sema.params.items.len); const comptime_params = try sema.arena.alloc(bool, sema.params.items.len); for (sema.params.items) |param, i| { - if (param.ty == .none) { + if (param.ty.tag() == .noreturn) { param_types[i] = Type.initTag(.noreturn); // indicates anytype } else { - // TODO make a compile error from `resolveType` report the source location - // of the specific parameter. Will need to take a similar strategy as - // `resolveSwitchItemVal` to avoid resolving the source location unless - // we actually need to report an error. - const param_src = src; - param_types[i] = try sema.analyzeAsType(block, param_src, param.ty); + param_types[i] = param.ty; } comptime_params[i] = param.is_comptime; any_are_comptime = any_are_comptime or param.is_comptime; @@ -3402,6 +3535,7 @@ fn funcCommon( .state = anal_state, .zir_body_inst = body_inst, .owner_decl = sema.owner_decl, + .comptime_args = if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr, .lbrace_line = src_locs.lbrace_line, .rbrace_line = src_locs.rbrace_line, .lbrace_column = @truncate(u16, src_locs.columns), @@ -6819,19 +6953,12 @@ fn safetyPanic( const msg_inst = msg_inst: { // TODO instead of making a new decl for every panic in the entire compilation, // introduce the concept of a reference-counted decl for these - var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); - errdefer new_decl_arena.deinit(); - - const decl_ty = try Type.Tag.array_u8.create(&new_decl_arena.allocator, msg.len); - const decl_val = try Value.Tag.bytes.create(&new_decl_arena.allocator, msg); - - const new_decl = try sema.mod.createAnonymousDecl(&block.base, .{ - .ty = decl_ty, - .val = decl_val, - }); - errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); - try new_decl.finalizeNewArena(&new_decl_arena); - break :msg_inst try sema.analyzeDeclRef(new_decl); + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + break :msg_inst try sema.analyzeDeclRef(try anon_decl.finish( + try Type.Tag.array_u8.create(anon_decl.arena(), msg.len), + try Value.Tag.bytes.create(anon_decl.arena(), msg), + )); }; const casted_msg_inst = try sema.coerce(block, Type.initTag(.const_slice_u8), msg_inst, src); @@ -8832,7 +8959,7 @@ fn addConstUndef(sema: *Sema, ty: Type) CompileError!Air.Inst.Ref { return sema.addConstant(ty, Value.initTag(.undef)); } -fn addConstant(sema: *Sema, ty: Type, val: Value) CompileError!Air.Inst.Ref { +pub fn addConstant(sema: *Sema, ty: Type, val: Value) SemaError!Air.Inst.Ref { const gpa = sema.gpa; const ty_inst = try sema.addType(ty); try sema.air_values.append(gpa, val); @@ -8888,3 +9015,10 @@ fn isComptimeKnown( ) !bool { return (try sema.resolveMaybeUndefVal(block, src, inst)) != null; } + +fn nextArgIsComptimeElided(sema: *Sema) bool { + if (sema.comptime_args.len == 0) return false; + const result = sema.comptime_args[sema.next_arg_index].val.tag() != .unreachable_value; + sema.next_arg_index += 1; + return result; +} diff --git a/src/Zir.zig b/src/Zir.zig index b7f4c28161..0b93208564 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -4909,6 +4909,7 @@ fn findDeclsBody( pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) struct { param_body: []const Inst.Index, body: []const Inst.Index, + total_params_len: u32, } { const tags = zir.instructions.items(.tag); const datas = zir.instructions.items(.data); @@ -4944,8 +4945,19 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) struct { }; assert(tags[info.param_block] == .block or tags[info.param_block] == .block_inline); const param_block = zir.extraData(Inst.Block, datas[info.param_block].pl_node.payload_index); + const param_body = zir.extra[param_block.end..][0..param_block.data.body_len]; + var total_params_len: u32 = 0; + for (param_body) |inst| { + switch (tags[inst]) { + .param, .param_comptime, .param_anytype, .param_anytype_comptime => { + total_params_len += 1; + }, + else => continue, + } + } return .{ - .param_body = zir.extra[param_block.end..][0..param_block.data.body_len], + .param_body = param_body, .body = info.body, + .total_params_len = total_params_len, }; } diff --git a/src/type.zig b/src/type.zig index feb16fd47c..237614e372 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1182,7 +1182,6 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, - .function, .single_const_pointer_to_comptime_int, .const_slice_u8, .array_u8_sentinel_0, @@ -1207,6 +1206,8 @@ pub const Type = extern union { .anyframe_T, => true, + .function => !self.castTag(.function).?.data.is_generic, + .@"struct" => { // TODO introduce lazy value mechanism const struct_obj = self.castTag(.@"struct").?.data; -- cgit v1.2.3 From d4468affb751668e156230c32b29c84684825b4f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Aug 2021 21:11:31 -0700 Subject: stage2 generics improvements: anytype and param type exprs AstGen result locations now have a `coerced_ty` tag which is the same as `ty` except it assumes that Sema will do a coercion, so it does not redundantly add an `as` instruction into the ZIR code. This results in cleaner ZIR and about a 14% reduction of ZIR bytes. param and param_comptime ZIR instructions now have a block body for their type expressions. This allows Sema to skip evaluation of the block in the case that the parameter is comptime-provided. It also allows a new mechanism to function: when evaluating type expressions of generic functions, if it would depend on another parameter, it returns `error.GenericPoison` which bubbles up and then is caught by the param/param_comptime instruction and then handled. This allows parameters to be evaluated independently so that the type info for functions which have comptime or anytype parameters will still have types populated for parameters that do not depend on values of previous parameters (because evaluation of their param blocks will return successfully instead of `error.GenericPoison`). It also makes iteration over the block that contains function parameters slightly more efficient since it now only contains the param instructions. Finally, it fixes the case where a generic function type expression contains a function prototype. Formerly, this situation would cause shared state to clobber each other; now it is in a proper tree structure so that can't happen. This fix also required adding a field to Sema `comptime_args_fn_inst` to make sure that the `comptime_args` field passed into Sema is applied to the correct `func` instruction. Source location for `node_offset_asm_ret_ty` is fixed; it was pointing at the asm output name rather than the return type as intended. Generic function instantiation is fixed, notably with respect to parameter type expressions that depend on previous parameters, and with respect to types which must be always comptime-known. This involves passing all the comptime arguments at a callsite of a generic function, and allowing the generic function semantic analysis to coerce the values to the proper types (since it has access to the evaluated parameter type expressions) and then decide based on the type whether the parameter is runtime known or not. In the case of explicitly marked `comptime` parameters, there is a check at the semantic analysis of the `call` instruction. Semantic analysis of `call` instructions does type coercion on the arguments, which is needed both for generic functions and to make up for using `coerced_ty` result locations (mentioned above). Tasks left in this branch: * Implement the memoization table. * Add test coverage. * Improve error reporting and source locations for compile errors. --- BRANCH_TODO | 4 - src/AstGen.zig | 71 +++++---- src/Compilation.zig | 2 +- src/Module.zig | 52 +++++-- src/Sema.zig | 428 ++++++++++++++++++++++++++++++++------------------- src/Zir.zig | 32 +++- src/codegen/llvm.zig | 4 + src/type.zig | 116 ++++++++++++++ src/value.zig | 55 ++----- test/cases.zig | 2 +- 10 files changed, 519 insertions(+), 247 deletions(-) delete mode 100644 BRANCH_TODO (limited to 'src/Module.zig') diff --git a/BRANCH_TODO b/BRANCH_TODO deleted file mode 100644 index e8606332d7..0000000000 --- a/BRANCH_TODO +++ /dev/null @@ -1,4 +0,0 @@ -* memoize the instantiation in a table -* expressions that depend on comptime stuff need a poison value to use for - types when generating the generic function type -* comptime anytype diff --git a/src/AstGen.zig b/src/AstGen.zig index 7534afe961..493e0a75f4 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -195,6 +195,9 @@ pub const ResultLoc = union(enum) { none_or_ref, /// The expression will be coerced into this type, but it will be evaluated as an rvalue. ty: Zir.Inst.Ref, + /// Same as `ty` but it is guaranteed that Sema will additionall perform the coercion, + /// so no `as` instruction needs to be emitted. + coerced_ty: Zir.Inst.Ref, /// The expression must store its result into this typed pointer. The result instruction /// from the expression must be ignored. ptr: Zir.Inst.Ref, @@ -225,7 +228,7 @@ pub const ResultLoc = union(enum) { fn strategy(rl: ResultLoc, block_scope: *GenZir) Strategy { switch (rl) { // In this branch there will not be any store_to_block_ptr instructions. - .discard, .none, .none_or_ref, .ty, .ref => return .{ + .discard, .none, .none_or_ref, .ty, .coerced_ty, .ref => return .{ .tag = .break_operand, .elide_store_to_block_ptr_instructions = false, }, @@ -260,13 +263,14 @@ pub const ResultLoc = union(enum) { pub const align_rl: ResultLoc = .{ .ty = .u16_type }; pub const bool_rl: ResultLoc = .{ .ty = .bool_type }; pub const type_rl: ResultLoc = .{ .ty = .type_type }; +pub const coerced_type_rl: ResultLoc = .{ .coerced_ty = .type_type }; fn typeExpr(gz: *GenZir, scope: *Scope, type_node: ast.Node.Index) InnerError!Zir.Inst.Ref { const prev_force_comptime = gz.force_comptime; gz.force_comptime = true; defer gz.force_comptime = prev_force_comptime; - return expr(gz, scope, .{ .ty = .type_type }, type_node); + return expr(gz, scope, coerced_type_rl, type_node); } /// Same as `expr` but fails with a compile error if the result type is `noreturn`. @@ -1079,16 +1083,19 @@ fn fnProtoExpr( .param_anytype; _ = try gz.addStrTok(tag, param_name, name_token); } else { + const gpa = astgen.gpa; const param_type_node = param.type_expr; assert(param_type_node != 0); - const param_type = try expr(gz, scope, type_rl, param_type_node); + var param_gz = gz.makeSubBlock(scope); + defer param_gz.instructions.deinit(gpa); + const param_type = try expr(¶m_gz, scope, coerced_type_rl, param_type_node); + const param_inst_expected = @intCast(u32, astgen.instructions.len + 1); + _ = try param_gz.addBreak(.break_inline, param_inst_expected, param_type); const main_tokens = tree.nodes.items(.main_token); const name_token = param.name_token orelse main_tokens[param_type_node]; const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param; - _ = try gz.addPlTok(tag, name_token, Zir.Inst.Param{ - .name = param_name, - .ty = param_type, - }); + const param_inst = try gz.addParam(tag, name_token, param_name, param_gz.instructions.items); + assert(param_inst_expected == param_inst); } } break :is_var_args false; @@ -1219,7 +1226,7 @@ fn arrayInitExpr( return arrayInitExprRlNone(gz, scope, node, array_init.ast.elements, .array_init_anon); } }, - .ty => |ty_inst| { + .ty, .coerced_ty => |ty_inst| { if (types.array != .none) { const result = try arrayInitExprRlTy(gz, scope, node, array_init.ast.elements, types.elem, .array_init); return rvalue(gz, rl, result, node); @@ -1388,7 +1395,7 @@ fn structInitExpr( return structInitExprRlNone(gz, scope, node, struct_init, .struct_init_anon); } }, - .ty => |ty_inst| { + .ty, .coerced_ty => |ty_inst| { if (struct_init.ast.type_expr == 0) { return structInitExprRlTy(gz, scope, node, struct_init, ty_inst, .struct_init); } @@ -2617,7 +2624,7 @@ fn assignOp( const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs); const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node); - const rhs = try expr(gz, scope, .{ .ty = lhs_type }, node_datas[infix_node].rhs); + const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs); const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{ .lhs = lhs, @@ -2953,14 +2960,18 @@ fn fnDecl( } else param: { const param_type_node = param.type_expr; assert(param_type_node != 0); - const param_type = try expr(&decl_gz, params_scope, type_rl, param_type_node); + var param_gz = decl_gz.makeSubBlock(scope); + defer param_gz.instructions.deinit(gpa); + const param_type = try expr(¶m_gz, params_scope, coerced_type_rl, param_type_node); + const param_inst_expected = @intCast(u32, astgen.instructions.len + 1); + _ = try param_gz.addBreak(.break_inline, param_inst_expected, param_type); + const main_tokens = tree.nodes.items(.main_token); const name_token = param.name_token orelse main_tokens[param_type_node]; const tag: Zir.Inst.Tag = if (is_comptime) .param_comptime else .param; - break :param try decl_gz.addPlTok(tag, name_token, Zir.Inst.Param{ - .name = param_name, - .ty = param_type, - }); + const param_inst = try decl_gz.addParam(tag, name_token, param_name, param_gz.instructions.items); + assert(param_inst_expected == param_inst); + break :param indexToRef(param_inst); }; if (param_name == 0) continue; @@ -6758,7 +6769,7 @@ fn as( ) InnerError!Zir.Inst.Ref { const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { - .none, .none_or_ref, .discard, .ref, .ty => { + .none, .none_or_ref, .discard, .ref, .ty, .coerced_ty => { const result = try reachableExpr(gz, scope, .{ .ty = dest_type }, rhs, node); return rvalue(gz, rl, result, node); }, @@ -6781,7 +6792,7 @@ fn unionInit( const union_type = try typeExpr(gz, scope, params[0]); const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); switch (rl) { - .none, .none_or_ref, .discard, .ref, .ty, .inferred_ptr => { + .none, .none_or_ref, .discard, .ref, .ty, .coerced_ty, .inferred_ptr => { _ = try gz.addPlNode(.field_type_ref, params[1], Zir.Inst.FieldTypeRef{ .container_type = union_type, .field_name = field_name, @@ -6867,7 +6878,7 @@ fn bitCast( const astgen = gz.astgen; const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { - .none, .none_or_ref, .discard, .ty => { + .none, .none_or_ref, .discard, .ty, .coerced_ty => { const operand = try expr(gz, scope, .none, rhs); const result = try gz.addPlNode(.bitcast, node, Zir.Inst.Bin{ .lhs = dest_type, @@ -7677,7 +7688,7 @@ fn callExpr( .param_index = @intCast(u32, i), } }, }); - args[i] = try expr(gz, scope, .{ .ty = param_type }, param_node); + args[i] = try expr(gz, scope, .{ .coerced_ty = param_type }, param_node); } const modifier: std.builtin.CallOptions.Modifier = blk: { @@ -8370,7 +8381,7 @@ fn rvalue( src_node: ast.Node.Index, ) InnerError!Zir.Inst.Ref { switch (rl) { - .none, .none_or_ref => return result, + .none, .none_or_ref, .coerced_ty => return result, .discard => { // Emit a compile error for discarding error values. _ = try gz.addUnNode(.ensure_result_non_error, result, src_node); @@ -9042,7 +9053,7 @@ const GenZir = struct { // we emit ZIR for the block break instructions to have the result values, // and then rvalue() on that to pass the value to the result location. switch (parent_rl) { - .ty => |ty_inst| { + .ty, .coerced_ty => |ty_inst| { gz.rl_ty_inst = ty_inst; gz.break_result_loc = parent_rl; }, @@ -9425,18 +9436,26 @@ const GenZir = struct { return indexToRef(new_index); } - fn addPlTok( + fn addParam( gz: *GenZir, tag: Zir.Inst.Tag, /// Absolute token index. This function does the conversion to Decl offset. abs_tok_index: ast.TokenIndex, - extra: anytype, - ) !Zir.Inst.Ref { + name: u32, + body: []const u32, + ) !Zir.Inst.Index { const gpa = gz.astgen.gpa; try gz.instructions.ensureUnusedCapacity(gpa, 1); try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Param).Struct.fields.len + + body.len); + + const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Param{ + .name = name, + .body_len = @intCast(u32, body.len), + }); + gz.astgen.extra.appendSliceAssumeCapacity(body); - const payload_index = try gz.astgen.addExtra(extra); const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); gz.astgen.instructions.appendAssumeCapacity(.{ .tag = tag, @@ -9446,7 +9465,7 @@ const GenZir = struct { } }, }); gz.instructions.appendAssumeCapacity(new_index); - return indexToRef(new_index); + return new_index; } fn addExtendedPayload( diff --git a/src/Compilation.zig b/src/Compilation.zig index f8f8cea328..adb4940243 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2118,7 +2118,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor if (builtin.mode == .Debug and self.verbose_air) { std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name}); @import("print_air.zig").dump(gpa, air, decl.namespace.file_scope.zir, liveness); - std.debug.print("# End Function AIR: {s}:\n", .{decl.name}); + std.debug.print("# End Function AIR: {s}\n\n", .{decl.name}); } self.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) { diff --git a/src/Module.zig b/src/Module.zig index 184ea617b1..2c3e745c11 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1173,6 +1173,8 @@ pub const Scope = struct { /// for the one that will be the same for all Block instances. src_decl: *Decl, instructions: ArrayListUnmanaged(Air.Inst.Index), + // `param` instructions are collected here to be used by the `func` instruction. + params: std.ArrayListUnmanaged(Param) = .{}, label: ?*Label = null, inlining: ?*Inlining, /// If runtime_index is not 0 then one of these is guaranteed to be non null. @@ -1187,6 +1189,12 @@ pub const Scope = struct { /// when null, it is determined by build mode, changed by @setRuntimeSafety want_safety: ?bool = null, + const Param = struct { + /// `noreturn` means `anytype`. + ty: Type, + is_comptime: bool, + }; + /// This `Block` maps a block ZIR instruction to the corresponding /// AIR instruction for break instruction analysis. pub const Label = struct { @@ -1634,8 +1642,11 @@ pub const SrcLoc = struct { .@"asm" => tree.asmFull(node), else => unreachable, }; + const asm_output = full.outputs[0]; + const node_datas = tree.nodes.items(.data); + const ret_ty_node = node_datas[asm_output].lhs; const main_tokens = tree.nodes.items(.main_token); - const tok_index = main_tokens[full.outputs[0]]; + const tok_index = main_tokens[ret_ty_node]; const token_starts = tree.tokens.items(.start); return token_starts[tok_index]; }, @@ -2099,7 +2110,20 @@ pub const LazySrcLoc = union(enum) { }; pub const SemaError = error{ OutOfMemory, AnalysisFail }; -pub const CompileError = error{ OutOfMemory, AnalysisFail, NeededSourceLocation }; +pub const CompileError = error{ + OutOfMemory, + /// When this is returned, the compile error for the failure has already been recorded. + AnalysisFail, + /// Returned when a compile error needed to be reported but a provided LazySrcLoc was set + /// to the `unneeded` tag. The source location was, in fact, needed. It is expected that + /// somewhere up the call stack, the operation will be retried after doing expensive work + /// to compute a source location. + NeededSourceLocation, + /// A Type or Value was needed to be used during semantic analysis, but it was not available + /// because the function is generic. This is only seen when analyzing the body of a param + /// instruction. + GenericPoison, +}; pub fn deinit(mod: *Module) void { const gpa = mod.gpa; @@ -2796,14 +2820,16 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) SemaError!void { } return error.AnalysisFail; }, - else => { + error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, + else => |e| { decl.analysis = .sema_failure_retryable; try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1); mod.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( mod.gpa, decl.srcLoc(), "unable to analyze: {s}", - .{@errorName(err)}, + .{@errorName(e)}, )); return error.AnalysisFail; }, @@ -2982,7 +3008,10 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { .inlining = null, .is_comptime = true, }; - defer block_scope.instructions.deinit(gpa); + defer { + block_scope.instructions.deinit(gpa); + block_scope.params.deinit(gpa); + } const zir_block_index = decl.zirBlockIndex(); const inst_data = zir_datas[zir_block_index].pl_node; @@ -3669,7 +3698,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType` try sema.inst_map.ensureUnusedCapacity(gpa, fn_info.total_params_len); - var param_index: usize = 0; + var runtime_param_index: usize = 0; + var total_param_index: usize = 0; for (fn_info.param_body) |inst| { const name = switch (zir_tags[inst]) { .param, .param_comptime => blk: { @@ -3686,16 +3716,16 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { else => continue, }; if (func.comptime_args) |comptime_args| { - const arg_tv = comptime_args[param_index]; + const arg_tv = comptime_args[total_param_index]; if (arg_tv.val.tag() != .unreachable_value) { // We have a comptime value for this parameter. const arg = try sema.addConstant(arg_tv.ty, arg_tv.val); sema.inst_map.putAssumeCapacityNoClobber(inst, arg); - param_index += 1; + total_param_index += 1; continue; } } - const param_type = fn_ty.fnParamType(param_index); + const param_type = fn_ty.fnParamType(runtime_param_index); const ty_ref = try sema.addType(param_type); const arg_index = @intCast(u32, sema.air_instructions.len); inner_block.instructions.appendAssumeCapacity(arg_index); @@ -3707,7 +3737,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { } }, }); sema.inst_map.putAssumeCapacityNoClobber(inst, Air.indexToRef(arg_index)); - param_index += 1; + total_param_index += 1; + runtime_param_index += 1; } func.state = .in_progress; @@ -3715,6 +3746,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { _ = sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) { error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, else => |e| return e, }; diff --git a/src/Sema.zig b/src/Sema.zig index 9a7eb6fc40..8bcdcb63c9 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -37,13 +37,15 @@ branch_count: u32 = 0, /// contain a mapped source location. src: LazySrcLoc = .{ .token_offset = 0 }, decl_val_table: std.AutoHashMapUnmanaged(*Decl, Air.Inst.Ref) = .{}, -/// `param` instructions are collected here to be used by the `func` instruction. -params: std.ArrayListUnmanaged(Param) = .{}, -/// When doing a generic function instantiation, this array collects a `Value` object for -/// each parameter that is comptime known and thus elided from the generated function. -/// This memory is allocated by a parent `Sema` and owned by the values arena of the owner_decl. +/// When doing a generic function instantiation, this array collects a +/// `Value` object for each parameter that is comptime known and thus elided +/// from the generated function. This memory is allocated by a parent `Sema` and +/// owned by the values arena of the Sema owner_decl. comptime_args: []TypedValue = &.{}, -next_arg_index: usize = 0, +/// Marks the function instruction that `comptime_args` applies to so that we +/// don't accidentally apply it to a function prototype which is used in the +/// type expression of a generic function parameter. +comptime_args_fn_inst: Zir.Inst.Index = 0, const std = @import("std"); const mem = std.mem; @@ -67,13 +69,6 @@ const LazySrcLoc = Module.LazySrcLoc; const RangeSet = @import("RangeSet.zig"); const target_util = @import("target.zig"); -const Param = struct { - name: [:0]const u8, - /// `noreturn` means `anytype`. - ty: Type, - is_comptime: bool, -}; - pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref); pub fn deinit(sema: *Sema) void { @@ -83,7 +78,6 @@ pub fn deinit(sema: *Sema) void { sema.air_values.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); - sema.params.deinit(gpa); sema.* = undefined; } @@ -466,6 +460,26 @@ pub fn analyzeBody( i += 1; continue; }, + .param => { + try sema.zirParam(block, inst, false); + i += 1; + continue; + }, + .param_comptime => { + try sema.zirParam(block, inst, true); + i += 1; + continue; + }, + .param_anytype => { + try sema.zirParamAnytype(block, inst, false); + i += 1; + continue; + }, + .param_anytype_comptime => { + try sema.zirParamAnytype(block, inst, true); + i += 1; + continue; + }, // Special case instructions to handle comptime control flow. .repeat_inline => { @@ -504,88 +518,6 @@ pub fn analyzeBody( return break_inst; } }, - .param => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; - const src = inst_data.src(); - const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; - const param_name = sema.code.nullTerminatedString(extra.name); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - const param_ty = try sema.resolveType(block, src, extra.ty); - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = param_ty, - .is_comptime = false, - }); - break :blk try sema.addConstUndef(param_ty); - }, - .param_comptime => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; - const src = inst_data.src(); - const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index).data; - const param_name = sema.code.nullTerminatedString(extra.name); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - const param_ty = try sema.resolveType(block, src, extra.ty); - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = param_ty, - .is_comptime = true, - }); - break :blk try sema.addConstUndef(param_ty); - }, - .param_anytype => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].str_tok; - const param_name = inst_data.get(sema.code); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = Type.initTag(.noreturn), - .is_comptime = false, - }); - break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); - }, - .param_anytype_comptime => blk: { - const inst_data = sema.code.instructions.items(.data)[inst].str_tok; - const param_name = inst_data.get(sema.code); - - if (sema.nextArgIsComptimeElided()) { - i += 1; - continue; - } - - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - - try sema.params.append(sema.gpa, .{ - .name = param_name, - .ty = Type.initTag(.noreturn), - .is_comptime = true, - }); - break :blk try sema.addConstUndef(Type.initTag(.@"undefined")); - }, }; if (sema.typeOf(air_inst).isNoReturn()) return always_noreturn; @@ -697,6 +629,7 @@ fn resolveValue( air_ref: Air.Inst.Ref, ) CompileError!Value { if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| { + if (val.tag() == .generic_poison) return error.GenericPoison; return val; } return sema.failWithNeededComptime(block, src); @@ -714,6 +647,7 @@ fn resolveConstValue( switch (val.tag()) { .undef => return sema.failWithUseOfUndef(block, src), .variable => return sema.failWithNeededComptime(block, src), + .generic_poison => return error.GenericPoison, else => return val, } } @@ -2422,7 +2356,7 @@ fn analyzeCall( call_src: LazySrcLoc, modifier: std.builtin.CallOptions.Modifier, ensure_result_used: bool, - args: []const Air.Inst.Ref, + uncasted_args: []const Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const mod = sema.mod; @@ -2444,22 +2378,22 @@ fn analyzeCall( const fn_params_len = func_ty_info.param_types.len; if (func_ty_info.is_var_args) { assert(cc == .C); - if (args.len < fn_params_len) { + if (uncasted_args.len < fn_params_len) { // TODO add error note: declared here return mod.fail( &block.base, func_src, "expected at least {d} argument(s), found {d}", - .{ fn_params_len, args.len }, + .{ fn_params_len, uncasted_args.len }, ); } - } else if (fn_params_len != args.len) { + } else if (fn_params_len != uncasted_args.len) { // TODO add error note: declared here return mod.fail( &block.base, func_src, "expected {d} argument(s), found {d}", - .{ fn_params_len, args.len }, + .{ fn_params_len, uncasted_args.len }, ); } @@ -2485,6 +2419,14 @@ fn analyzeCall( const is_inline_call = is_comptime_call or modifier == .always_inline or func_ty_info.cc == .Inline; const result: Air.Inst.Ref = if (is_inline_call) res: { + // TODO look into not allocating this args array + const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); + for (uncasted_args) |uncasted_arg, i| { + const param_ty = func_ty.fnParamType(i); + const arg_src = call_src; // TODO: better source location + args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src); + } + const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = switch (func_val.tag()) { .function => func_val.castTag(.function).?.data, @@ -2574,13 +2516,12 @@ fn analyzeCall( const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = func_val.castTag(.function).?.data; // Check the Module's generic function map with an adapted context, so that we - // can match against `args` rather than doing the work below to create a generic Scope - // only to junk it if it matches an existing instantiation. + // can match against `uncasted_args` rather than doing the work below to create a + // generic Scope only to junk it if it matches an existing instantiation. // TODO const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); const zir_tags = sema.code.instructions.items(.tag); - var non_comptime_args_len: u32 = 0; const new_func = new_func: { const namespace = module_fn.owner_decl.namespace; try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); @@ -2622,7 +2563,8 @@ fn analyzeCall( .namespace = namespace, .func = null, .owner_func = null, - .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, args.len), + .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), + .comptime_args_fn_inst = module_fn.zir_body_inst, }; defer child_sema.deinit(); @@ -2634,41 +2576,59 @@ fn analyzeCall( .inlining = null, .is_comptime = true, }; - defer child_block.instructions.deinit(gpa); + defer { + child_block.instructions.deinit(gpa); + child_block.params.deinit(gpa); + } - try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, args.len)); + try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); var arg_i: usize = 0; for (fn_info.param_body) |inst| { const is_comptime = switch (zir_tags[inst]) { .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => false, // TODO make true for always comptime types + .param, .param_anytype => false, else => continue, }; - if (is_comptime) { - // TODO: pass .unneeded to resolveConstValue and then if we get - // error.NeededSourceLocation resolve the arg source location and - // try again. - const arg_src = call_src; - const arg = args[arg_i]; - const arg_val = try sema.resolveConstValue(block, arg_src, arg); - child_sema.comptime_args[arg_i] = .{ - .ty = try sema.typeOf(arg).copy(&new_decl_arena.allocator), - .val = try arg_val.copy(&new_decl_arena.allocator), - }; + // TODO: pass .unneeded to resolveConstValue and then if we get + // error.NeededSourceLocation resolve the arg source location and + // try again. + const arg_src = call_src; + const arg = uncasted_args[arg_i]; + if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else { - non_comptime_args_len += 1; + } else if (is_comptime) { + return sema.failWithNeededComptime(block, arg_src); + } + arg_i += 1; + } + const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); + const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); + const new_func = new_func_val.castTag(.function).?.data; + + arg_i = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, + } + const arg = child_sema.inst_map.get(inst).?; + const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; + + if (arg_val.tag() == .generic_poison) { child_sema.comptime_args[arg_i] = .{ .ty = Type.initTag(.noreturn), .val = Value.initTag(.unreachable_value), }; + } else { + child_sema.comptime_args[arg_i] = .{ + .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), + .val = try arg_val.copy(&new_decl_arena.allocator), + }; } + arg_i += 1; } - const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); - const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); - const new_func = new_func_val.castTag(.function).?.data; // Populate the Decl ty/val with the function and its type. new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); @@ -2690,31 +2650,72 @@ fn analyzeCall( // Make a runtime call to the new function, making sure to omit the comptime args. try sema.requireRuntimeBlock(block, call_src); + const new_func_val = sema.resolveConstValue(block, .unneeded, new_func) catch unreachable; + const new_module_func = new_func_val.castTag(.function).?.data; + const comptime_args = new_module_func.comptime_args.?; + const runtime_args_len = count: { + var count: u32 = 0; + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => { + if (comptime_args[arg_i].val.tag() == .unreachable_value) { + count += 1; + } + arg_i += 1; + }, + else => continue, + } + } + break :count count; + }; + const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); + { + const new_fn_ty = new_module_func.owner_decl.ty; + var runtime_i: u32 = 0; + var total_i: u32 = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, + } + const is_runtime = comptime_args[total_i].val.tag() == .unreachable_value; + if (is_runtime) { + const param_ty = new_fn_ty.fnParamType(runtime_i); + const arg_src = call_src; // TODO: better source location + const uncasted_arg = uncasted_args[total_i]; + const casted_arg = try sema.coerce(block, param_ty, uncasted_arg, arg_src); + runtime_args[runtime_i] = casted_arg; + runtime_i += 1; + } + total_i += 1; + } + } try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + - non_comptime_args_len); + runtime_args_len); const func_inst = try block.addInst(.{ .tag = .call, .data = .{ .pl_op = .{ .operand = new_func, .payload = sema.addExtraAssumeCapacity(Air.Call{ - .args_len = non_comptime_args_len, + .args_len = runtime_args_len, }), } }, }); - var arg_i: usize = 0; - for (fn_info.param_body) |inst| { - const is_comptime = switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => false, // TODO make true for always comptime types - else => continue, - }; - if (is_comptime) { - sema.air_extra.appendAssumeCapacity(@enumToInt(args[arg_i])); - } - arg_i += 1; - } + sema.appendRefsAssumeCapacity(runtime_args); break :res func_inst; } else res: { + const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); + for (uncasted_args) |uncasted_arg, i| { + if (i < fn_params_len) { + const param_ty = func_ty.fnParamType(i); + const arg_src = call_src; // TODO: better source location + args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src); + } else { + args[i] = uncasted_arg; + } + } + try sema.requireRuntimeBlock(block, call_src); try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + args.len); @@ -3416,7 +3417,7 @@ fn funcCommon( const fn_ty: Type = fn_ty: { // Hot path for some common function types. - if (sema.params.items.len == 0 and !var_args and align_val.tag() == .null_value and + if (block.params.items.len == 0 and !var_args and align_val.tag() == .null_value and !inferred_error_set) { if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) { @@ -3436,19 +3437,15 @@ fn funcCommon( } } - var any_are_comptime = false; - const param_types = try sema.arena.alloc(Type, sema.params.items.len); - const comptime_params = try sema.arena.alloc(bool, sema.params.items.len); - for (sema.params.items) |param, i| { - if (param.ty.tag() == .noreturn) { - param_types[i] = Type.initTag(.noreturn); // indicates anytype - } else { - param_types[i] = param.ty; - } + var is_generic = false; + const param_types = try sema.arena.alloc(Type, block.params.items.len); + const comptime_params = try sema.arena.alloc(bool, block.params.items.len); + for (block.params.items) |param, i| { + param_types[i] = param.ty; comptime_params[i] = param.is_comptime; - any_are_comptime = any_are_comptime or param.is_comptime; + is_generic = is_generic or param.is_comptime or + param.ty.tag() == .generic_poison or param.ty.requiresComptime(); } - sema.params.clearRetainingCapacity(); if (align_val.tag() != .null_value) { return mod.fail(&block.base, src, "TODO implement support for function prototypes to have alignment specified", .{}); @@ -3471,7 +3468,7 @@ fn funcCommon( .return_type = return_type, .cc = cc, .is_var_args = var_args, - .is_generic = any_are_comptime, + .is_generic = is_generic, }); }; @@ -3530,12 +3527,16 @@ fn funcCommon( const is_inline = fn_ty.fnCallingConvention() == .Inline; const anal_state: Module.Fn.Analysis = if (is_inline) .inline_only else .queued; + const comptime_args: ?[*]TypedValue = if (sema.comptime_args_fn_inst == body_inst) blk: { + break :blk if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr; + } else null; + const fn_payload = try sema.arena.create(Value.Payload.Function); new_func.* = .{ .state = anal_state, .zir_body_inst = body_inst, .owner_decl = sema.owner_decl, - .comptime_args = if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr, + .comptime_args = comptime_args, .lbrace_line = src_locs.lbrace_line, .rbrace_line = src_locs.rbrace_line, .lbrace_column = @truncate(u16, src_locs.columns), @@ -3548,6 +3549,113 @@ fn funcCommon( return sema.addConstant(fn_ty, Value.initPayload(&fn_payload.base)); } +fn zirParam( + sema: *Sema, + block: *Scope.Block, + inst: Zir.Inst.Index, + is_comptime: bool, +) CompileError!void { + const inst_data = sema.code.instructions.items(.data)[inst].pl_tok; + const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index); + const param_name = sema.code.nullTerminatedString(extra.data.name); + const body = sema.code.extra[extra.end..][0..extra.data.body_len]; + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + _ = param_name; + + // We could be in a generic function instantiation, or we could be evaluating a generic + // function without any comptime args provided. + const param_ty = param_ty: { + const err = err: { + // Make sure any nested param instructions don't clobber our work. + const prev_params = block.params; + block.params = .{}; + defer { + block.params.deinit(sema.gpa); + block.params = prev_params; + } + + if (sema.resolveBody(block, body)) |param_ty_inst| { + if (sema.analyzeAsType(block, src, param_ty_inst)) |param_ty| { + break :param_ty param_ty; + } else |err| break :err err; + } else |err| break :err err; + }; + switch (err) { + error.GenericPoison => { + // The type is not available until the generic instantiation. + // We result the param instruction with a poison value and + // insert an anytype parameter. + try block.params.append(sema.gpa, .{ + .ty = Type.initTag(.generic_poison), + .is_comptime = is_comptime, + }); + try sema.inst_map.putNoClobber(sema.gpa, inst, .generic_poison); + return; + }, + else => |e| return e, + } + }; + if (sema.inst_map.get(inst)) |arg| { + if (is_comptime or param_ty.requiresComptime()) { + // We have a comptime value for this parameter so it should be elided from the + // function type of the function instruction in this block. + const coerced_arg = try sema.coerce(block, param_ty, arg, src); + sema.inst_map.putAssumeCapacity(inst, coerced_arg); + return; + } + // Even though a comptime argument is provided, the generic function wants to treat + // this as a runtime parameter. + assert(sema.inst_map.remove(inst)); + } + + try block.params.append(sema.gpa, .{ + .ty = param_ty, + .is_comptime = is_comptime, + }); + const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); + try sema.inst_map.putNoClobber(sema.gpa, inst, result); +} + +fn zirParamAnytype( + sema: *Sema, + block: *Scope.Block, + inst: Zir.Inst.Index, + is_comptime: bool, +) CompileError!void { + const inst_data = sema.code.instructions.items(.data)[inst].str_tok; + const param_name = inst_data.get(sema.code); + + // TODO check if param_name shadows a Decl. This only needs to be done if + // usingnamespace is implemented. + _ = param_name; + + if (sema.inst_map.get(inst)) |air_ref| { + const param_ty = sema.typeOf(air_ref); + if (is_comptime or param_ty.requiresComptime()) { + // We have a comptime value for this parameter so it should be elided from the + // function type of the function instruction in this block. + return; + } + // The map is already populated but we do need to add a runtime parameter. + try block.params.append(sema.gpa, .{ + .ty = param_ty, + .is_comptime = false, + }); + return; + } + + // We are evaluating a generic function without any comptime args provided. + + try block.params.append(sema.gpa, .{ + .ty = Type.initTag(.generic_poison), + .is_comptime = is_comptime, + }); + try sema.inst_map.put(sema.gpa, inst, .generic_poison); +} + fn zirAs(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -7618,8 +7726,10 @@ fn coerce( inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - if (dest_type_unresolved.tag() == .var_args_param) { - return sema.coerceVarArgParam(block, inst, inst_src); + switch (dest_type_unresolved.tag()) { + .var_args_param => return sema.coerceVarArgParam(block, inst, inst_src), + .generic_poison => return inst, + else => {}, } const dest_type_src = inst_src; // TODO better source location const dest_type = try sema.resolveTypeFields(block, dest_type_src, dest_type_unresolved); @@ -8820,6 +8930,7 @@ fn typeHasOnePossibleValue( .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, + .generic_poison => unreachable, }; } @@ -8942,6 +9053,8 @@ pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref { .fn_ccc_void_no_args => return .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int => return .single_const_pointer_to_comptime_int_type, .const_slice_u8 => return .const_slice_u8_type, + .anyerror_void_error_union => return .anyerror_void_error_union_type, + .generic_poison => return .generic_poison_type, else => {}, } try sema.air_instructions.append(sema.gpa, .{ @@ -9015,10 +9128,3 @@ fn isComptimeKnown( ) !bool { return (try sema.resolveMaybeUndefVal(block, src, inst)) != null; } - -fn nextArgIsComptimeElided(sema: *Sema) bool { - if (sema.comptime_args.len == 0) return false; - const result = sema.comptime_args[sema.next_arg_index].val.tag() != .unreachable_value; - sema.next_arg_index += 1; - return result; -} diff --git a/src/Zir.zig b/src/Zir.zig index 0b93208564..b4cbd9c875 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -1704,6 +1704,8 @@ pub const Inst = struct { fn_ccc_void_no_args_type, single_const_pointer_to_comptime_int_type, const_slice_u8_type, + anyerror_void_error_union_type, + generic_poison_type, /// `undefined` (untyped) undef, @@ -1731,6 +1733,9 @@ pub const Inst = struct { calling_convention_c, /// `std.builtin.CallingConvention.Inline` calling_convention_inline, + /// Used for generic parameters where the type and value + /// is not known until generic function instantiation. + generic_poison, _, @@ -1909,6 +1914,14 @@ pub const Inst = struct { .ty = Type.initTag(.type), .val = Value.initTag(.const_slice_u8_type), }, + .anyerror_void_error_union_type = .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.anyerror_void_error_union_type), + }, + .generic_poison_type = .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.generic_poison_type), + }, .enum_literal_type = .{ .ty = Type.initTag(.type), .val = Value.initTag(.enum_literal_type), @@ -2006,6 +2019,10 @@ pub const Inst = struct { .ty = Type.initTag(.calling_convention), .val = .{ .ptr_otherwise = &calling_convention_inline_payload.base }, }, + .generic_poison = .{ + .ty = Type.initTag(.generic_poison), + .val = Value.initTag(.generic_poison), + }, }); }; @@ -2787,10 +2804,12 @@ pub const Inst = struct { args: Ref, }; + /// Trailing: inst: Index // for every body_len pub const Param = struct { /// Null-terminated string index. name: u32, - ty: Ref, + /// The body contains the type of the parameter. + body_len: u32, }; /// Trailing: @@ -3348,11 +3367,16 @@ const Writer = struct { fn writeParam(self: *Writer, stream: anytype, inst: Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_tok; - const extra = self.code.extraData(Inst.Param, inst_data.payload_index).data; + const extra = self.code.extraData(Inst.Param, inst_data.payload_index); + const body = self.code.extra[extra.end..][0..extra.data.body_len]; try stream.print("\"{}\", ", .{ - std.zig.fmtEscapes(self.code.nullTerminatedString(extra.name)), + std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)), }); - try self.writeInstRef(stream, extra.ty); + try stream.writeAll("{\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); try stream.writeAll(") "); try self.writeSrc(stream, inst_data.src()); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 8b3edcfe23..4a589ea66d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -839,6 +839,10 @@ pub const DeclGen = struct { .False, ); }, + .ComptimeInt => unreachable, + .ComptimeFloat => unreachable, + .Type => unreachable, + .EnumLiteral => unreachable, else => return self.todo("implement const of type '{}'", .{tv.ty}), } } diff --git a/src/type.zig b/src/type.zig index 237614e372..0e99a929f0 100644 --- a/src/type.zig +++ b/src/type.zig @@ -130,6 +130,7 @@ pub const Type = extern union { => return .Union, .var_args_param => unreachable, // can be any type + .generic_poison => unreachable, // must be handled earlier } } @@ -699,6 +700,7 @@ pub const Type = extern union { .export_options, .extern_options, .@"anyframe", + .generic_poison, => unreachable, .array_u8, @@ -1083,11 +1085,117 @@ pub const Type = extern union { }, .inferred_alloc_const => return writer.writeAll("(inferred_alloc_const)"), .inferred_alloc_mut => return writer.writeAll("(inferred_alloc_mut)"), + .generic_poison => return writer.writeAll("(generic poison)"), } unreachable; } } + /// Anything that reports hasCodeGenBits() false returns false here as well. + pub fn requiresComptime(ty: Type) bool { + return switch (ty.tag()) { + .u1, + .u8, + .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, + .u128, + .i128, + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .f16, + .f32, + .f64, + .f128, + .c_void, + .bool, + .void, + .anyerror, + .noreturn, + .@"anyframe", + .@"null", + .@"undefined", + .atomic_ordering, + .atomic_rmw_op, + .calling_convention, + .float_mode, + .reduce_op, + .call_options, + .export_options, + .extern_options, + .manyptr_u8, + .manyptr_const_u8, + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + .single_const_pointer_to_comptime_int, + .const_slice_u8, + .anyerror_void_error_union, + .empty_struct_literal, + .function, + .empty_struct, + .error_set, + .error_set_single, + .error_set_inferred, + .@"opaque", + => false, + + .type, + .comptime_int, + .comptime_float, + .enum_literal, + => true, + + .var_args_param => unreachable, + .inferred_alloc_mut => unreachable, + .inferred_alloc_const => unreachable, + .generic_poison => unreachable, + + .array_u8, + .array_u8_sentinel_0, + .array, + .array_sentinel, + .vector, + .pointer, + .single_const_pointer, + .single_mut_pointer, + .many_const_pointer, + .many_mut_pointer, + .c_const_pointer, + .c_mut_pointer, + .const_slice, + .mut_slice, + .int_signed, + .int_unsigned, + .optional, + .optional_single_mut_pointer, + .optional_single_const_pointer, + .error_union, + .anyframe_T, + .@"struct", + .@"union", + .union_tagged, + .enum_simple, + .enum_full, + .enum_nonexhaustive, + => false, // TODO some of these should be `true` depending on their child types + }; + } + pub fn toValue(self: Type, allocator: *Allocator) Allocator.Error!Value { switch (self.tag()) { .u1 => return Value.initTag(.u1_type), @@ -1287,6 +1395,7 @@ pub const Type = extern union { .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, .var_args_param => unreachable, + .generic_poison => unreachable, }; } @@ -1509,6 +1618,8 @@ pub const Type = extern union { .@"opaque", .var_args_param, => unreachable, + + .generic_poison => unreachable, }; } @@ -1536,6 +1647,7 @@ pub const Type = extern union { .inferred_alloc_mut => unreachable, .@"opaque" => unreachable, .var_args_param => unreachable, + .generic_poison => unreachable, .@"struct" => { const s = self.castTag(.@"struct").?.data; @@ -1702,6 +1814,7 @@ pub const Type = extern union { .inferred_alloc_mut => unreachable, .@"opaque" => unreachable, .var_args_param => unreachable, + .generic_poison => unreachable, .@"struct" => { @panic("TODO bitSize struct"); @@ -2626,6 +2739,7 @@ pub const Type = extern union { .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, + .generic_poison => unreachable, }; } @@ -3039,6 +3153,7 @@ pub const Type = extern union { single_const_pointer_to_comptime_int, const_slice_u8, anyerror_void_error_union, + generic_poison, /// This is a special type for variadic parameters of a function call. /// Casts to it will validate that the type can be passed to a c calling convetion function. var_args_param, @@ -3136,6 +3251,7 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int, .anyerror_void_error_union, .const_slice_u8, + .generic_poison, .inferred_alloc_const, .inferred_alloc_mut, .var_args_param, diff --git a/src/value.zig b/src/value.zig index 134b51e494..bd1cc57416 100644 --- a/src/value.zig +++ b/src/value.zig @@ -76,6 +76,8 @@ pub const Value = extern union { fn_ccc_void_no_args_type, single_const_pointer_to_comptime_int_type, const_slice_u8_type, + anyerror_void_error_union_type, + generic_poison_type, undef, zero, @@ -85,6 +87,7 @@ pub const Value = extern union { null_value, bool_true, bool_false, + generic_poison, abi_align_default, empty_struct_value, @@ -188,6 +191,8 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .anyframe_type, .const_slice_u8_type, + .anyerror_void_error_union_type, + .generic_poison_type, .enum_literal_type, .undef, .zero, @@ -210,6 +215,7 @@ pub const Value = extern union { .call_options_type, .export_options_type, .extern_options_type, + .generic_poison, => @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"), .int_big_positive, @@ -366,6 +372,8 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .anyframe_type, .const_slice_u8_type, + .anyerror_void_error_union_type, + .generic_poison_type, .enum_literal_type, .undef, .zero, @@ -388,6 +396,7 @@ pub const Value = extern union { .call_options_type, .export_options_type, .extern_options_type, + .generic_poison, => unreachable, .ty => { @@ -556,6 +565,9 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"), .anyframe_type => return out_stream.writeAll("anyframe"), .const_slice_u8_type => return out_stream.writeAll("[]const u8"), + .anyerror_void_error_union_type => return out_stream.writeAll("anyerror!void"), + .generic_poison_type => return out_stream.writeAll("(generic poison type)"), + .generic_poison => return out_stream.writeAll("(generic poison)"), .enum_literal_type => return out_stream.writeAll("@Type(.EnumLiteral)"), .manyptr_u8_type => return out_stream.writeAll("[*]u8"), .manyptr_const_u8_type => return out_stream.writeAll("[*]const u8"), @@ -709,6 +721,8 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int), .anyframe_type => Type.initTag(.@"anyframe"), .const_slice_u8_type => Type.initTag(.const_slice_u8), + .anyerror_void_error_union_type => Type.initTag(.anyerror_void_error_union), + .generic_poison_type => Type.initTag(.generic_poison), .enum_literal_type => Type.initTag(.enum_literal), .manyptr_u8_type => Type.initTag(.manyptr_u8), .manyptr_const_u8_type => Type.initTag(.manyptr_const_u8), @@ -732,46 +746,7 @@ pub const Value = extern union { return Type.initPayload(&buffer.base); }, - .undef, - .zero, - .one, - .void_value, - .unreachable_value, - .empty_array, - .bool_true, - .bool_false, - .null_value, - .int_u64, - .int_i64, - .int_big_positive, - .int_big_negative, - .function, - .extern_fn, - .variable, - .decl_ref, - .decl_ref_mut, - .elem_ptr, - .field_ptr, - .bytes, - .repeated, - .array, - .slice, - .float_16, - .float_32, - .float_64, - .float_128, - .enum_literal, - .enum_field_index, - .@"error", - .error_union, - .empty_struct_value, - .@"struct", - .@"union", - .inferred_alloc, - .inferred_alloc_comptime, - .abi_align_default, - .eu_payload_ptr, - => unreachable, + else => unreachable, }; } diff --git a/test/cases.zig b/test/cases.zig index 840ee7a4ac..a342b91a6f 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1572,7 +1572,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ const x = asm volatile ("syscall" \\ : [o] "{rax}" (-> number) \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) + \\ [arg1] "{rdi}" (60) \\ : "rcx", "r11", "memory" \\ ); \\ _ = x; -- cgit v1.2.3 From e9e3a2994696a3131125ebc4b1f0eec7ca5306d9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Aug 2021 16:37:21 -0700 Subject: stage2: implement generic function memoization Module has a new field `monomorphed_funcs` which stores the set of `*Module.Fn` objects which are generic function instantiations. The hash is based on hashes of comptime values of parameters known to be comptime based on an explicit comptime keyword or must-be-comptime type expressions that can be evaluated without performing monomorphization. This allows function calls to be semantically analyzed cheaply for generic functions which are already instantiated. The table is updated with a single `getOrPutAdapted` in the semantic analysis of `call` instructions, by pre-allocating the `Fn` object and passing it to the child `Sema`. --- src/Module.zig | 44 ++++ src/Sema.zig | 460 ++++++++++++++++++++++++-------------- src/Zir.zig | 6 +- src/type.zig | 34 +-- src/value.zig | 145 ++++++------ test/behavior.zig | 3 +- test/behavior/basic.zig | 70 ++++++ test/behavior/generics.zig | 169 +------------- test/behavior/generics_stage1.zig | 169 ++++++++++++++ test/behavior/misc.zig | 72 +----- 10 files changed, 679 insertions(+), 493 deletions(-) create mode 100644 test/behavior/generics_stage1.zig (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index 2c3e745c11..2556ad3f0e 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -61,6 +61,11 @@ export_owners: std.AutoArrayHashMapUnmanaged(*Decl, []*Export) = .{}, /// Keys are fully resolved file paths. This table owns the keys and values. import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{}, +/// The set of all the generic function instantiations. This is used so that when a generic +/// function is called twice with the same comptime parameter arguments, both calls dispatch +/// to the same function. +monomorphed_funcs: MonomorphedFuncsSet = .{}, + /// We optimize memory usage for a compilation with no compile errors by storing the /// error messages and mapping outside of `Decl`. /// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator. @@ -114,6 +119,44 @@ emit_h: ?*GlobalEmitH, test_functions: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{}, +const MonomorphedFuncsSet = std.HashMapUnmanaged( + *Fn, + void, + MonomorphedFuncsContext, + std.hash_map.default_max_load_percentage, +); + +const MonomorphedFuncsContext = struct { + pub fn eql(ctx: @This(), a: *Fn, b: *Fn) bool { + _ = ctx; + return a == b; + } + + /// Must match `Sema.GenericCallAdapter.hash`. + pub fn hash(ctx: @This(), key: *Fn) u64 { + _ = ctx; + var hasher = std.hash.Wyhash.init(0); + + // The generic function Decl is guaranteed to be the first dependency + // of each of its instantiations. + const generic_owner_decl = key.owner_decl.dependencies.keys()[0]; + const generic_func = generic_owner_decl.val.castTag(.function).?.data; + std.hash.autoHash(&hasher, @ptrToInt(generic_func)); + + // This logic must be kept in sync with the logic in `analyzeCall` that + // computes the hash. + const comptime_args = key.comptime_args.?; + const generic_ty_info = generic_owner_decl.ty.fnInfo(); + for (generic_ty_info.param_types) |param_ty, i| { + if (generic_ty_info.paramIsComptime(i) and param_ty.tag() != .generic_poison) { + comptime_args[i].val.hash(param_ty, &hasher); + } + } + + return hasher.final(); + } +}; + /// A `Module` has zero or one of these depending on whether `-femit-h` is enabled. pub const GlobalEmitH = struct { /// Where to put the output. @@ -2205,6 +2248,7 @@ pub fn deinit(mod: *Module) void { mod.error_name_list.deinit(gpa); mod.test_functions.deinit(gpa); + mod.monomorphed_funcs.deinit(gpa); } fn freeExportList(gpa: *Allocator, export_list: []*Export) void { diff --git a/src/Sema.zig b/src/Sema.zig index dff11301db..0557587fb5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -46,6 +46,12 @@ comptime_args: []TypedValue = &.{}, /// don't accidentally apply it to a function prototype which is used in the /// type expression of a generic function parameter. comptime_args_fn_inst: Zir.Inst.Index = 0, +/// When `comptime_args` is provided, this field is also provided. It was used as +/// the key in the `monomorphed_funcs` set. The `func` instruction is supposed +/// to use this instead of allocating a fresh one. This avoids an unnecessary +/// extra hash table lookup in the `monomorphed_funcs` set. +/// Sema will set this to null when it takes ownership. +preallocated_new_func: ?*Module.Fn = null, const std = @import("std"); const mem = std.mem; @@ -2354,6 +2360,40 @@ fn zirCall( return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args); } +const GenericCallAdapter = struct { + generic_fn: *Module.Fn, + precomputed_hash: u64, + func_ty_info: Type.Payload.Function.Data, + comptime_vals: []const Value, + + pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool { + _ = adapted_key; + // The generic function Decl is guaranteed to be the first dependency + // of each of its instantiations. + const generic_owner_decl = other_key.owner_decl.dependencies.keys()[0]; + if (ctx.generic_fn.owner_decl != generic_owner_decl) return false; + + // This logic must be kept in sync with the logic in `analyzeCall` that + // computes the hash. + const other_comptime_args = other_key.comptime_args.?; + for (ctx.func_ty_info.param_types) |param_ty, i| { + if (ctx.func_ty_info.paramIsComptime(i) and param_ty.tag() != .generic_poison) { + if (!ctx.comptime_vals[i].eql(other_comptime_args[i].val, param_ty)) { + return false; + } + } + } + return true; + } + + /// The implementation of the hash is in semantic analysis of function calls, so + /// that any errors when computing the hash can be properly reported. + pub fn hash(ctx: @This(), adapted_key: void) u64 { + _ = adapted_key; + return ctx.precomputed_hash; + } +}; + fn analyzeCall( sema: *Sema, block: *Scope.Block, @@ -2524,193 +2564,192 @@ fn analyzeCall( // Check the Module's generic function map with an adapted context, so that we // can match against `uncasted_args` rather than doing the work below to create a // generic Scope only to junk it if it matches an existing instantiation. - // TODO - const namespace = module_fn.owner_decl.namespace; const fn_zir = namespace.file_scope.zir; const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst); const zir_tags = fn_zir.instructions.items(.tag); - const new_func = new_func: { - try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); - - // Create a Decl for the new function. - const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node); - // TODO better names for generic function instantiations - const name_index = mod.getNextAnonNameIndex(); - new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ - module_fn.owner_decl.name, name_index, - }); - new_decl.src_line = module_fn.owner_decl.src_line; - new_decl.is_pub = module_fn.owner_decl.is_pub; - new_decl.is_exported = module_fn.owner_decl.is_exported; - new_decl.has_align = module_fn.owner_decl.has_align; - new_decl.has_linksection = module_fn.owner_decl.has_linksection; - new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; - new_decl.alive = true; // This Decl is called at runtime. - new_decl.has_tv = true; - new_decl.owns_tv = true; - new_decl.analysis = .in_progress; - new_decl.generation = mod.generation; - - namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); - - var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); - errdefer new_decl_arena.deinit(); - - // Re-run the block that creates the function, with the comptime parameters - // pre-populated inside `inst_map`. This causes `param_comptime` and - // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a - // new, monomorphized function, with the comptime parameters elided. - var child_sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = sema.arena, - .code = fn_zir, - .owner_decl = new_decl, - .namespace = namespace, - .func = null, - .owner_func = null, - .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), - .comptime_args_fn_inst = module_fn.zir_body_inst, - }; - defer child_sema.deinit(); - - var child_block: Scope.Block = .{ - .parent = null, - .sema = &child_sema, - .src_decl = new_decl, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - }; - defer { - child_block.instructions.deinit(gpa); - child_block.params.deinit(gpa); - } - - try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); - var arg_i: usize = 0; - for (fn_info.param_body) |inst| { - const is_comptime = switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => false, - else => continue, - }; - // TODO: pass .unneeded to resolveConstValue and then if we get - // error.NeededSourceLocation resolve the arg source location and - // try again. - const arg_src = call_src; - const arg = uncasted_args[arg_i]; - if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { - const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else if (is_comptime) { - return sema.failWithNeededComptime(block, arg_src); + const new_module_func = new_func: { + // This hash must match `Module.MonomorphedFuncsContext.hash`. + // For parameters explicitly marked comptime and simple parameter type expressions, + // we know whether a parameter is elided from a monomorphed function, and can + // use it in the hash here. However, for parameter type expressions that are not + // explicitly marked comptime and rely on previous parameter comptime values, we + // don't find out until after generating a monomorphed function whether the parameter + // type ended up being a "must-be-comptime-known" type. + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, @ptrToInt(module_fn)); + + const comptime_vals = try sema.arena.alloc(Value, func_ty_info.param_types.len); + + for (func_ty_info.param_types) |param_ty, i| { + const is_comptime = func_ty_info.paramIsComptime(i); + if (is_comptime and param_ty.tag() != .generic_poison) { + const arg_src = call_src; // TODO better source location + const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src); + if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| { + arg_val.hash(param_ty, &hasher); + comptime_vals[i] = arg_val; + } else { + return sema.failWithNeededComptime(block, arg_src); + } } - arg_i += 1; } - const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); - const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); - const new_func = new_func_val.castTag(.function).?.data; - - arg_i = 0; - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, - else => continue, - } - const arg = child_sema.inst_map.get(inst).?; - const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; - if (arg_val.tag() == .generic_poison) { - child_sema.comptime_args[arg_i] = .{ - .ty = Type.initTag(.noreturn), - .val = Value.initTag(.unreachable_value), - }; - } else { - child_sema.comptime_args[arg_i] = .{ - .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), - .val = try arg_val.copy(&new_decl_arena.allocator), - }; - } - - arg_i += 1; + const adapter: GenericCallAdapter = .{ + .generic_fn = module_fn, + .precomputed_hash = hasher.final(), + .func_ty_info = func_ty_info, + .comptime_vals = comptime_vals, + }; + const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); + if (gop.found_existing) { + const callee_func = gop.key_ptr.*; + break :res try sema.finishGenericCall( + block, + call_src, + callee_func, + func_src, + uncasted_args, + fn_info, + zir_tags, + ); } + gop.key_ptr.* = try gpa.create(Module.Fn); + break :new_func gop.key_ptr.*; + }; - // Populate the Decl ty/val with the function and its type. - new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); - new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); - new_decl.analysis = .complete; + try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); - // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field - // will be populated, ensuring it will have `analyzeBody` called with the ZIR - // parameters mapped appropriately. - try mod.comp.bin_file.allocateDeclIndexes(new_decl); - try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); + // Create a Decl for the new function. + const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node); + // TODO better names for generic function instantiations + const name_index = mod.getNextAnonNameIndex(); + new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ + module_fn.owner_decl.name, name_index, + }); + new_decl.src_line = module_fn.owner_decl.src_line; + new_decl.is_pub = module_fn.owner_decl.is_pub; + new_decl.is_exported = module_fn.owner_decl.is_exported; + new_decl.has_align = module_fn.owner_decl.has_align; + new_decl.has_linksection = module_fn.owner_decl.has_linksection; + new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; + new_decl.alive = true; // This Decl is called at runtime. + new_decl.has_tv = true; + new_decl.owns_tv = true; + new_decl.analysis = .in_progress; + new_decl.generation = mod.generation; + + namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + + // Re-run the block that creates the function, with the comptime parameters + // pre-populated inside `inst_map`. This causes `param_comptime` and + // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a + // new, monomorphized function, with the comptime parameters elided. + var child_sema: Sema = .{ + .mod = mod, + .gpa = gpa, + .arena = sema.arena, + .code = fn_zir, + .owner_decl = new_decl, + .namespace = namespace, + .func = null, + .owner_func = null, + .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), + .comptime_args_fn_inst = module_fn.zir_body_inst, + .preallocated_new_func = new_module_func, + }; + defer child_sema.deinit(); - try new_decl.finalizeNewArena(&new_decl_arena); - break :new_func try sema.analyzeDeclVal(block, func_src, new_decl); + var child_block: Scope.Block = .{ + .parent = null, + .sema = &child_sema, + .src_decl = new_decl, + .instructions = .{}, + .inlining = null, + .is_comptime = true, }; + defer { + child_block.instructions.deinit(gpa); + child_block.params.deinit(gpa); + } - // Save it into the Module's generic function map. - // TODO + try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + const is_comptime = switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime => true, + .param, .param_anytype => false, + else => continue, + } or func_ty_info.paramIsComptime(arg_i); + const arg_src = call_src; // TODO: better source location + const arg = uncasted_args[arg_i]; + if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { + const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else if (is_comptime) { + return sema.failWithNeededComptime(block, arg_src); + } + arg_i += 1; + } + const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); + const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); + const new_func = new_func_val.castTag(.function).?.data; + assert(new_func == new_module_func); - // Make a runtime call to the new function, making sure to omit the comptime args. - try sema.requireRuntimeBlock(block, call_src); - const new_func_val = sema.resolveConstValue(block, .unneeded, new_func) catch unreachable; - const new_module_func = new_func_val.castTag(.function).?.data; - const comptime_args = new_module_func.comptime_args.?; - const runtime_args_len = count: { - var count: u32 = 0; - var arg_i: usize = 0; - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime, .param, .param_anytype => { - if (comptime_args[arg_i].val.tag() == .unreachable_value) { - count += 1; - } - arg_i += 1; - }, - else => continue, - } + arg_i = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, } - break :count count; - }; - const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); - { - const new_fn_ty = new_module_func.owner_decl.ty; - var runtime_i: u32 = 0; - var total_i: u32 = 0; - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, - else => continue, - } - const is_runtime = comptime_args[total_i].val.tag() == .unreachable_value; - if (is_runtime) { - const param_ty = new_fn_ty.fnParamType(runtime_i); - const arg_src = call_src; // TODO: better source location - const uncasted_arg = uncasted_args[total_i]; - const casted_arg = try sema.coerce(block, param_ty, uncasted_arg, arg_src); - runtime_args[runtime_i] = casted_arg; - runtime_i += 1; - } - total_i += 1; + const arg = child_sema.inst_map.get(inst).?; + const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; + + if (arg_val.tag() == .generic_poison) { + child_sema.comptime_args[arg_i] = .{ + .ty = Type.initTag(.noreturn), + .val = Value.initTag(.unreachable_value), + }; + } else { + child_sema.comptime_args[arg_i] = .{ + .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), + .val = try arg_val.copy(&new_decl_arena.allocator), + }; } + + arg_i += 1; } - try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + - runtime_args_len); - const func_inst = try block.addInst(.{ - .tag = .call, - .data = .{ .pl_op = .{ - .operand = new_func, - .payload = sema.addExtraAssumeCapacity(Air.Call{ - .args_len = runtime_args_len, - }), - } }, - }); - sema.appendRefsAssumeCapacity(runtime_args); - break :res func_inst; + + // Populate the Decl ty/val with the function and its type. + new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); + new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); + new_decl.analysis = .complete; + + // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field + // will be populated, ensuring it will have `analyzeBody` called with the ZIR + // parameters mapped appropriately. + try mod.comp.bin_file.allocateDeclIndexes(new_decl); + try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); + + try new_decl.finalizeNewArena(&new_decl_arena); + + // The generic function Decl is guaranteed to be the first dependency + // of each of its instantiations. + assert(new_decl.dependencies.keys().len == 0); + try mod.declareDeclDependency(new_decl, module_fn.owner_decl); + + break :res try sema.finishGenericCall( + block, + call_src, + new_module_func, + func_src, + uncasted_args, + fn_info, + zir_tags, + ); } else res: { const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); for (uncasted_args) |uncasted_arg, i| { @@ -2745,6 +2784,75 @@ fn analyzeCall( return result; } +fn finishGenericCall( + sema: *Sema, + block: *Scope.Block, + call_src: LazySrcLoc, + callee: *Module.Fn, + func_src: LazySrcLoc, + uncasted_args: []const Air.Inst.Ref, + fn_info: Zir.FnInfo, + zir_tags: []const Zir.Inst.Tag, +) CompileError!Air.Inst.Ref { + const callee_inst = try sema.analyzeDeclVal(block, func_src, callee.owner_decl); + + // Make a runtime call to the new function, making sure to omit the comptime args. + try sema.requireRuntimeBlock(block, call_src); + + const comptime_args = callee.comptime_args.?; + const runtime_args_len = count: { + var count: u32 = 0; + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => { + if (comptime_args[arg_i].val.tag() == .unreachable_value) { + count += 1; + } + arg_i += 1; + }, + else => continue, + } + } + break :count count; + }; + const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); + { + const new_fn_ty = callee.owner_decl.ty; + var runtime_i: u32 = 0; + var total_i: u32 = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, + } + const is_runtime = comptime_args[total_i].val.tag() == .unreachable_value; + if (is_runtime) { + const param_ty = new_fn_ty.fnParamType(runtime_i); + const arg_src = call_src; // TODO: better source location + const uncasted_arg = uncasted_args[total_i]; + const casted_arg = try sema.coerce(block, param_ty, uncasted_arg, arg_src); + runtime_args[runtime_i] = casted_arg; + runtime_i += 1; + } + total_i += 1; + } + } + try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len + + runtime_args_len); + const func_inst = try block.addInst(.{ + .tag = .call, + .data = .{ .pl_op = .{ + .operand = callee_inst, + .payload = sema.addExtraAssumeCapacity(Air.Call{ + .args_len = runtime_args_len, + }), + } }, + }); + sema.appendRefsAssumeCapacity(runtime_args); + return func_inst; +} + fn zirIntType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { _ = block; const tracy = trace(@src()); @@ -3419,7 +3527,15 @@ fn funcCommon( const mod = sema.mod; - const new_func = if (body_inst == 0) undefined else try sema.gpa.create(Module.Fn); + const new_func: *Module.Fn = new_func: { + if (body_inst == 0) break :new_func undefined; + if (sema.comptime_args_fn_inst == body_inst) { + const new_func = sema.preallocated_new_func.?; + sema.preallocated_new_func = null; // take ownership + break :new_func new_func; + } + break :new_func try sema.gpa.create(Module.Fn); + }; errdefer if (body_inst != 0) sema.gpa.destroy(new_func); const fn_ty: Type = fn_ty: { @@ -3620,7 +3736,7 @@ fn zirParam( try block.params.append(sema.gpa, .{ .ty = param_ty, - .is_comptime = is_comptime, + .is_comptime = is_comptime or param_ty.requiresComptime(), }); const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); try sema.inst_map.putNoClobber(sema.gpa, inst, result); diff --git a/src/Zir.zig b/src/Zir.zig index b4cbd9c875..862af7b033 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -4930,11 +4930,13 @@ fn findDeclsBody( } } -pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) struct { +pub const FnInfo = struct { param_body: []const Inst.Index, body: []const Inst.Index, total_params_len: u32, -} { +}; + +pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { const tags = zir.instructions.items(.tag); const datas = zir.instructions.items(.data); const info: struct { diff --git a/src/type.zig b/src/type.zig index 0e99a929f0..180fb92bf0 100644 --- a/src/type.zig +++ b/src/type.zig @@ -549,8 +549,13 @@ pub const Type = extern union { pub fn hash(self: Type) u64 { var hasher = std.hash.Wyhash.init(0); + self.hashWithHasher(&hasher); + return hasher.final(); + } + + pub fn hashWithHasher(self: Type, hasher: *std.hash.Wyhash) void { const zig_type_tag = self.zigTypeTag(); - std.hash.autoHash(&hasher, zig_type_tag); + std.hash.autoHash(hasher, zig_type_tag); switch (zig_type_tag) { .Type, .Void, @@ -568,34 +573,34 @@ pub const Type = extern union { .Int => { // Detect that e.g. u64 != usize, even if the bits match on a particular target. if (self.isNamedInt()) { - std.hash.autoHash(&hasher, self.tag()); + std.hash.autoHash(hasher, self.tag()); } else { // Remaining cases are arbitrary sized integers. // The target will not be branched upon, because we handled target-dependent cases above. const info = self.intInfo(@as(Target, undefined)); - std.hash.autoHash(&hasher, info.signedness); - std.hash.autoHash(&hasher, info.bits); + std.hash.autoHash(hasher, info.signedness); + std.hash.autoHash(hasher, info.bits); } }, .Array, .Vector => { - std.hash.autoHash(&hasher, self.arrayLen()); - std.hash.autoHash(&hasher, self.elemType().hash()); + std.hash.autoHash(hasher, self.arrayLen()); + std.hash.autoHash(hasher, self.elemType().hash()); // TODO hash array sentinel }, .Fn => { - std.hash.autoHash(&hasher, self.fnReturnType().hash()); - std.hash.autoHash(&hasher, self.fnCallingConvention()); + std.hash.autoHash(hasher, self.fnReturnType().hash()); + std.hash.autoHash(hasher, self.fnCallingConvention()); const params_len = self.fnParamLen(); - std.hash.autoHash(&hasher, params_len); + std.hash.autoHash(hasher, params_len); var i: usize = 0; while (i < params_len) : (i += 1) { - std.hash.autoHash(&hasher, self.fnParamType(i).hash()); + std.hash.autoHash(hasher, self.fnParamType(i).hash()); } - std.hash.autoHash(&hasher, self.fnIsVarArgs()); + std.hash.autoHash(hasher, self.fnIsVarArgs()); }, .Optional => { var buf: Payload.ElemType = undefined; - std.hash.autoHash(&hasher, self.optionalChild(&buf).hash()); + std.hash.autoHash(hasher, self.optionalChild(&buf).hash()); }, .Float, .Struct, @@ -612,7 +617,6 @@ pub const Type = extern union { // TODO implement more type hashing }, } - return hasher.final(); } pub const HashContext64 = struct { @@ -3373,7 +3377,7 @@ pub const Type = extern union { data: Data, // TODO look into optimizing this memory to take fewer bytes - const Data = struct { + pub const Data = struct { param_types: []Type, comptime_params: [*]bool, return_type: Type, @@ -3381,7 +3385,7 @@ pub const Type = extern union { is_var_args: bool, is_generic: bool, - fn paramIsComptime(self: @This(), i: usize) bool { + pub fn paramIsComptime(self: @This(), i: usize) bool { if (!self.is_generic) return false; assert(i < self.param_types.len); return self.comptime_params[i]; diff --git a/src/value.zig b/src/value.zig index bd1cc57416..bf80c9d831 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1117,12 +1117,82 @@ pub const Value = extern union { return order(a, b).compare(.eq); } + pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void { + switch (ty.zigTypeTag()) { + .BoundFn => unreachable, // TODO remove this from the language + + .Void, + .NoReturn, + .Undefined, + .Null, + => {}, + + .Type => { + var buf: ToTypeBuffer = undefined; + return val.toType(&buf).hashWithHasher(hasher); + }, + .Bool => { + std.hash.autoHash(hasher, val.toBool()); + }, + .Int, .ComptimeInt => { + var space: BigIntSpace = undefined; + const big = val.toBigInt(&space); + std.hash.autoHash(hasher, big.positive); + for (big.limbs) |limb| { + std.hash.autoHash(hasher, limb); + } + }, + .Float, .ComptimeFloat => { + @panic("TODO implement hashing float values"); + }, + .Pointer => { + @panic("TODO implement hashing pointer values"); + }, + .Array, .Vector => { + @panic("TODO implement hashing array/vector values"); + }, + .Struct => { + @panic("TODO implement hashing struct values"); + }, + .Optional => { + @panic("TODO implement hashing optional values"); + }, + .ErrorUnion => { + @panic("TODO implement hashing error union values"); + }, + .ErrorSet => { + @panic("TODO implement hashing error set values"); + }, + .Enum => { + @panic("TODO implement hashing enum values"); + }, + .Union => { + @panic("TODO implement hashing union values"); + }, + .Fn => { + @panic("TODO implement hashing function values"); + }, + .Opaque => { + @panic("TODO implement hashing opaque values"); + }, + .Frame => { + @panic("TODO implement hashing frame values"); + }, + .AnyFrame => { + @panic("TODO implement hashing anyframe values"); + }, + .EnumLiteral => { + @panic("TODO implement hashing enum literal values"); + }, + } + } + pub const ArrayHashContext = struct { ty: Type, - pub fn hash(self: @This(), v: Value) u32 { + pub fn hash(self: @This(), val: Value) u32 { const other_context: HashContext = .{ .ty = self.ty }; - return @truncate(u32, other_context.hash(v)); + return @truncate(u32, other_context.hash(val)); } pub fn eql(self: @This(), a: Value, b: Value) bool { return a.eql(b, self.ty); @@ -1132,76 +1202,9 @@ pub const Value = extern union { pub const HashContext = struct { ty: Type, - pub fn hash(self: @This(), v: Value) u64 { + pub fn hash(self: @This(), val: Value) u64 { var hasher = std.hash.Wyhash.init(0); - - switch (self.ty.zigTypeTag()) { - .BoundFn => unreachable, // TODO remove this from the language - - .Void, - .NoReturn, - .Undefined, - .Null, - => {}, - - .Type => { - var buf: ToTypeBuffer = undefined; - return v.toType(&buf).hash(); - }, - .Bool => { - std.hash.autoHash(&hasher, v.toBool()); - }, - .Int, .ComptimeInt => { - var space: BigIntSpace = undefined; - const big = v.toBigInt(&space); - std.hash.autoHash(&hasher, big.positive); - for (big.limbs) |limb| { - std.hash.autoHash(&hasher, limb); - } - }, - .Float, .ComptimeFloat => { - @panic("TODO implement hashing float values"); - }, - .Pointer => { - @panic("TODO implement hashing pointer values"); - }, - .Array, .Vector => { - @panic("TODO implement hashing array/vector values"); - }, - .Struct => { - @panic("TODO implement hashing struct values"); - }, - .Optional => { - @panic("TODO implement hashing optional values"); - }, - .ErrorUnion => { - @panic("TODO implement hashing error union values"); - }, - .ErrorSet => { - @panic("TODO implement hashing error set values"); - }, - .Enum => { - @panic("TODO implement hashing enum values"); - }, - .Union => { - @panic("TODO implement hashing union values"); - }, - .Fn => { - @panic("TODO implement hashing function values"); - }, - .Opaque => { - @panic("TODO implement hashing opaque values"); - }, - .Frame => { - @panic("TODO implement hashing frame values"); - }, - .AnyFrame => { - @panic("TODO implement hashing anyframe values"); - }, - .EnumLiteral => { - @panic("TODO implement hashing enum literal values"); - }, - } + val.hash(self.ty, &hasher); return hasher.final(); } diff --git a/test/behavior.zig b/test/behavior.zig index 4cc4d7fb8b..26272cb2fd 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -4,6 +4,7 @@ test { // Tests that pass for both. _ = @import("behavior/bool.zig"); _ = @import("behavior/basic.zig"); + _ = @import("behavior/generics.zig"); if (!builtin.zig_is_stage2) { // Tests that only pass for stage1. @@ -94,7 +95,7 @@ test { _ = @import("behavior/fn_in_struct_in_comptime.zig"); _ = @import("behavior/fn_delegation.zig"); _ = @import("behavior/for.zig"); - _ = @import("behavior/generics.zig"); + _ = @import("behavior/generics_stage1.zig"); _ = @import("behavior/hasdecl.zig"); _ = @import("behavior/hasfield.zig"); _ = @import("behavior/if.zig"); diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index ac1dc3889c..1372dfaeeb 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -92,3 +92,73 @@ fn first4KeysOfHomeRow() []const u8 { test "return string from function" { try expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu")); } + +test "hex escape" { + try expect(mem.eql(u8, "\x68\x65\x6c\x6c\x6f", "hello")); +} + +test "multiline string" { + const s1 = + \\one + \\two) + \\three + ; + const s2 = "one\ntwo)\nthree"; + try expect(mem.eql(u8, s1, s2)); +} + +test "multiline string comments at start" { + const s1 = + //\\one + \\two) + \\three + ; + const s2 = "two)\nthree"; + try expect(mem.eql(u8, s1, s2)); +} + +test "multiline string comments at end" { + const s1 = + \\one + \\two) + //\\three + ; + const s2 = "one\ntwo)"; + try expect(mem.eql(u8, s1, s2)); +} + +test "multiline string comments in middle" { + const s1 = + \\one + //\\two) + \\three + ; + const s2 = "one\nthree"; + try expect(mem.eql(u8, s1, s2)); +} + +test "multiline string comments at multiple places" { + const s1 = + \\one + //\\two + \\three + //\\four + \\five + ; + const s2 = "one\nthree\nfive"; + try expect(mem.eql(u8, s1, s2)); +} + +test "call result of if else expression" { + try expect(mem.eql(u8, f2(true), "a")); + try expect(mem.eql(u8, f2(false), "b")); +} +fn f2(x: bool) []const u8 { + return (if (x) fA else fB)(); +} +fn fA() []const u8 { + return "a"; +} +fn fB() []const u8 { + return "b"; +} diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index 104752607a..e3fed907df 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -3,167 +3,14 @@ const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; -test "simple generic fn" { - try expect(max(i32, 3, -1) == 3); - try expect(max(f32, 0.123, 0.456) == 0.456); - try expect(add(2, 3) == 5); +test "one param, explicit comptime" { + var x: usize = 0; + x += checkSize(i32); + x += checkSize(bool); + x += checkSize(bool); + try expect(x == 6); } -fn max(comptime T: type, a: T, b: T) T { - return if (a > b) a else b; -} - -fn add(comptime a: i32, b: i32) i32 { - return (comptime a) + b; -} - -const the_max = max(u32, 1234, 5678); -test "compile time generic eval" { - try expect(the_max == 5678); -} - -fn gimmeTheBigOne(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn shouldCallSameInstance(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn sameButWithFloats(a: f64, b: f64) f64 { - return max(f64, a, b); -} - -test "fn with comptime args" { - try expect(gimmeTheBigOne(1234, 5678) == 5678); - try expect(shouldCallSameInstance(34, 12) == 34); - try expect(sameButWithFloats(0.43, 0.49) == 0.49); -} - -test "var params" { - try expect(max_i32(12, 34) == 34); - try expect(max_f64(1.2, 3.4) == 3.4); -} - -test { - comptime try expect(max_i32(12, 34) == 34); - comptime try expect(max_f64(1.2, 3.4) == 3.4); -} - -fn max_var(a: anytype, b: anytype) @TypeOf(a + b) { - return if (a > b) a else b; -} - -fn max_i32(a: i32, b: i32) i32 { - return max_var(a, b); -} - -fn max_f64(a: f64, b: f64) f64 { - return max_var(a, b); -} - -pub fn List(comptime T: type) type { - return SmallList(T, 8); -} - -pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) type { - return struct { - items: []T, - length: usize, - prealloc_items: [STATIC_SIZE]T, - }; -} - -test "function with return type type" { - var list: List(i32) = undefined; - var list2: List(i32) = undefined; - list.length = 10; - list2.length = 10; - try expect(list.prealloc_items.len == 8); - try expect(list2.prealloc_items.len == 8); -} - -test "generic struct" { - var a1 = GenNode(i32){ - .value = 13, - .next = null, - }; - var b1 = GenNode(bool){ - .value = true, - .next = null, - }; - try expect(a1.value == 13); - try expect(a1.value == a1.getVal()); - try expect(b1.getVal()); -} -fn GenNode(comptime T: type) type { - return struct { - value: T, - next: ?*GenNode(T), - fn getVal(n: *const GenNode(T)) T { - return n.value; - } - }; -} - -test "const decls in struct" { - try expect(GenericDataThing(3).count_plus_one == 4); -} -fn GenericDataThing(comptime count: isize) type { - return struct { - const count_plus_one = count + 1; - }; -} - -test "use generic param in generic param" { - try expect(aGenericFn(i32, 3, 4) == 7); -} -fn aGenericFn(comptime T: type, comptime a: T, b: T) T { - return a + b; -} - -test "generic fn with implicit cast" { - try expect(getFirstByte(u8, &[_]u8{13}) == 13); - try expect(getFirstByte(u16, &[_]u16{ - 0, - 13, - }) == 0); -} -fn getByte(ptr: ?*const u8) u8 { - return ptr.?.*; -} -fn getFirstByte(comptime T: type, mem: []const T) u8 { - return getByte(@ptrCast(*const u8, &mem[0])); -} - -const foos = [_]fn (anytype) bool{ - foo1, - foo2, -}; - -fn foo1(arg: anytype) bool { - return arg; -} -fn foo2(arg: anytype) bool { - return !arg; -} - -test "array of generic fns" { - try expect(foos[0](true)); - try expect(!foos[1](true)); -} - -test "generic fn keeps non-generic parameter types" { - const A = 128; - - const S = struct { - fn f(comptime T: type, s: []T) !void { - try expect(A != @typeInfo(@TypeOf(s)).Pointer.alignment); - } - }; - - // The compiler monomorphizes `S.f` for `T=u8` on its first use, check that - // `x` type not affect `s` parameter type. - var x: [16]u8 align(A) = undefined; - try S.f(u8, &x); +fn checkSize(comptime T: type) usize { + return @sizeOf(T); } diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig new file mode 100644 index 0000000000..104752607a --- /dev/null +++ b/test/behavior/generics_stage1.zig @@ -0,0 +1,169 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectEqual = testing.expectEqual; + +test "simple generic fn" { + try expect(max(i32, 3, -1) == 3); + try expect(max(f32, 0.123, 0.456) == 0.456); + try expect(add(2, 3) == 5); +} + +fn max(comptime T: type, a: T, b: T) T { + return if (a > b) a else b; +} + +fn add(comptime a: i32, b: i32) i32 { + return (comptime a) + b; +} + +const the_max = max(u32, 1234, 5678); +test "compile time generic eval" { + try expect(the_max == 5678); +} + +fn gimmeTheBigOne(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn shouldCallSameInstance(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn sameButWithFloats(a: f64, b: f64) f64 { + return max(f64, a, b); +} + +test "fn with comptime args" { + try expect(gimmeTheBigOne(1234, 5678) == 5678); + try expect(shouldCallSameInstance(34, 12) == 34); + try expect(sameButWithFloats(0.43, 0.49) == 0.49); +} + +test "var params" { + try expect(max_i32(12, 34) == 34); + try expect(max_f64(1.2, 3.4) == 3.4); +} + +test { + comptime try expect(max_i32(12, 34) == 34); + comptime try expect(max_f64(1.2, 3.4) == 3.4); +} + +fn max_var(a: anytype, b: anytype) @TypeOf(a + b) { + return if (a > b) a else b; +} + +fn max_i32(a: i32, b: i32) i32 { + return max_var(a, b); +} + +fn max_f64(a: f64, b: f64) f64 { + return max_var(a, b); +} + +pub fn List(comptime T: type) type { + return SmallList(T, 8); +} + +pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) type { + return struct { + items: []T, + length: usize, + prealloc_items: [STATIC_SIZE]T, + }; +} + +test "function with return type type" { + var list: List(i32) = undefined; + var list2: List(i32) = undefined; + list.length = 10; + list2.length = 10; + try expect(list.prealloc_items.len == 8); + try expect(list2.prealloc_items.len == 8); +} + +test "generic struct" { + var a1 = GenNode(i32){ + .value = 13, + .next = null, + }; + var b1 = GenNode(bool){ + .value = true, + .next = null, + }; + try expect(a1.value == 13); + try expect(a1.value == a1.getVal()); + try expect(b1.getVal()); +} +fn GenNode(comptime T: type) type { + return struct { + value: T, + next: ?*GenNode(T), + fn getVal(n: *const GenNode(T)) T { + return n.value; + } + }; +} + +test "const decls in struct" { + try expect(GenericDataThing(3).count_plus_one == 4); +} +fn GenericDataThing(comptime count: isize) type { + return struct { + const count_plus_one = count + 1; + }; +} + +test "use generic param in generic param" { + try expect(aGenericFn(i32, 3, 4) == 7); +} +fn aGenericFn(comptime T: type, comptime a: T, b: T) T { + return a + b; +} + +test "generic fn with implicit cast" { + try expect(getFirstByte(u8, &[_]u8{13}) == 13); + try expect(getFirstByte(u16, &[_]u16{ + 0, + 13, + }) == 0); +} +fn getByte(ptr: ?*const u8) u8 { + return ptr.?.*; +} +fn getFirstByte(comptime T: type, mem: []const T) u8 { + return getByte(@ptrCast(*const u8, &mem[0])); +} + +const foos = [_]fn (anytype) bool{ + foo1, + foo2, +}; + +fn foo1(arg: anytype) bool { + return arg; +} +fn foo2(arg: anytype) bool { + return !arg; +} + +test "array of generic fns" { + try expect(foos[0](true)); + try expect(!foos[1](true)); +} + +test "generic fn keeps non-generic parameter types" { + const A = 128; + + const S = struct { + fn f(comptime T: type, s: []T) !void { + try expect(A != @typeInfo(@TypeOf(s)).Pointer.alignment); + } + }; + + // The compiler monomorphizes `S.f` for `T=u8` on its first use, check that + // `x` type not affect `s` parameter type. + var x: [16]u8 align(A) = undefined; + try S.f(u8, &x); +} diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig index 8a0761dfd8..466be00bd3 100644 --- a/test/behavior/misc.zig +++ b/test/behavior/misc.zig @@ -40,10 +40,6 @@ test "constant equal function pointers" { fn emptyFn() void {} -test "hex escape" { - try expect(mem.eql(u8, "\x68\x65\x6c\x6c\x6f", "hello")); -} - test "string concatenation" { try expect(mem.eql(u8, "OK" ++ " IT " ++ "WORKED", "OK IT WORKED")); } @@ -62,59 +58,7 @@ test "string escapes" { try expectEqualStrings("\u{1234}\u{069}\u{1}", "\xe1\x88\xb4\x69\x01"); } -test "multiline string" { - const s1 = - \\one - \\two) - \\three - ; - const s2 = "one\ntwo)\nthree"; - try expect(mem.eql(u8, s1, s2)); -} - -test "multiline string comments at start" { - const s1 = - //\\one - \\two) - \\three - ; - const s2 = "two)\nthree"; - try expect(mem.eql(u8, s1, s2)); -} - -test "multiline string comments at end" { - const s1 = - \\one - \\two) - //\\three - ; - const s2 = "one\ntwo)"; - try expect(mem.eql(u8, s1, s2)); -} - -test "multiline string comments in middle" { - const s1 = - \\one - //\\two) - \\three - ; - const s2 = "one\nthree"; - try expect(mem.eql(u8, s1, s2)); -} - -test "multiline string comments at multiple places" { - const s1 = - \\one - //\\two - \\three - //\\four - \\five - ; - const s2 = "one\nthree\nfive"; - try expect(mem.eql(u8, s1, s2)); -} - -test "multiline C string" { +test "multiline string literal is null terminated" { const s1 = \\one \\two) @@ -169,20 +113,6 @@ fn outer() i64 { return inner(); } -test "call result of if else expression" { - try expect(mem.eql(u8, f2(true), "a")); - try expect(mem.eql(u8, f2(false), "b")); -} -fn f2(x: bool) []const u8 { - return (if (x) fA else fB)(); -} -fn fA() []const u8 { - return "a"; -} -fn fB() []const u8 { - return "b"; -} - test "constant enum initialization with differing sizes" { try test3_1(test3_foo); try test3_2(test3_bar); -- cgit v1.2.3 From c03a04a58942446b48e9294df991a17a3a6f7b48 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Aug 2021 19:15:59 -0700 Subject: stage2: return type expressions of generic functions * ZIR encoding for function instructions have a body for the return type. This lets Sema for generic functions do the same thing it does for parameters, handling `error.GenericPoison` in the evaluation of the return type by marking the function as generic. * Sema: fix missing block around the new Decl arena finalization. This led to a memory corruption. * Added some floating point support to the LLVM backend but didn't get far enough to pass any new tests. --- src/AstGen.zig | 70 +++++----- src/Module.zig | 3 + src/Sema.zig | 284 ++++++++++++++++++++++---------------- src/Zir.zig | 64 +++++++-- src/codegen/llvm.zig | 16 ++- src/codegen/llvm/bindings.zig | 18 +++ src/print_air.zig | 2 +- test/behavior/generics.zig | 56 ++++++++ test/behavior/generics_stage1.zig | 39 +----- 9 files changed, 344 insertions(+), 208 deletions(-) (limited to 'src/Module.zig') diff --git a/src/AstGen.zig b/src/AstGen.zig index 9886e4d809..febe581e35 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1041,6 +1041,7 @@ fn fnProtoExpr( fn_proto: ast.full.FnProto, ) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; + const gpa = astgen.gpa; const tree = astgen.tree; const token_tags = tree.tokens.items(.tag); @@ -1083,7 +1084,6 @@ fn fnProtoExpr( .param_anytype; _ = try gz.addStrTok(tag, param_name, name_token); } else { - const gpa = astgen.gpa; const param_type_node = param.type_expr; assert(param_type_node != 0); var param_gz = gz.makeSubBlock(scope); @@ -1113,15 +1113,13 @@ fn fnProtoExpr( if (is_inferred_error) { return astgen.failTok(maybe_bang, "function prototype may not have inferred error set", .{}); } - const return_type_inst = try AstGen.expr( - gz, - scope, - .{ .ty = .type_type }, - fn_proto.ast.return_type, - ); + var ret_gz = gz.makeSubBlock(scope); + defer ret_gz.instructions.deinit(gpa); + const ret_ty = try expr(&ret_gz, scope, coerced_type_rl, fn_proto.ast.return_type); + const ret_br = try ret_gz.addBreak(.break_inline, 0, ret_ty); const cc: Zir.Inst.Ref = if (fn_proto.ast.callconv_expr != 0) - try AstGen.expr( + try expr( gz, scope, .{ .ty = .calling_convention_type }, @@ -1133,7 +1131,8 @@ fn fnProtoExpr( const result = try gz.addFunc(.{ .src_node = fn_proto.ast.proto_node, .param_block = 0, - .ret_ty = return_type_inst, + .ret_ty = ret_gz.instructions.items, + .ret_br = ret_br, .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = align_inst, @@ -3005,12 +3004,10 @@ fn fnDecl( break :inst try comptimeExpr(&decl_gz, params_scope, .{ .ty = .const_slice_u8_type }, fn_proto.ast.section_expr); }; - const return_type_inst = try AstGen.expr( - &decl_gz, - params_scope, - .{ .ty = .type_type }, - fn_proto.ast.return_type, - ); + var ret_gz = gz.makeSubBlock(params_scope); + defer ret_gz.instructions.deinit(gpa); + const ret_ty = try expr(&decl_gz, params_scope, coerced_type_rl, fn_proto.ast.return_type); + const ret_br = try ret_gz.addBreak(.break_inline, 0, ret_ty); const cc: Zir.Inst.Ref = blk: { if (fn_proto.ast.callconv_expr != 0) { @@ -3021,7 +3018,7 @@ fn fnDecl( .{}, ); } - break :blk try AstGen.expr( + break :blk try expr( &decl_gz, params_scope, .{ .ty = .calling_convention_type }, @@ -3046,7 +3043,8 @@ fn fnDecl( } break :func try decl_gz.addFunc(.{ .src_node = decl_node, - .ret_ty = return_type_inst, + .ret_ty = ret_gz.instructions.items, + .ret_br = ret_br, .param_block = block_inst, .body = &[0]Zir.Inst.Index{}, .cc = cc, @@ -3085,7 +3083,8 @@ fn fnDecl( break :func try decl_gz.addFunc(.{ .src_node = decl_node, .param_block = block_inst, - .ret_ty = return_type_inst, + .ret_ty = ret_gz.instructions.items, + .ret_br = ret_br, .body = fn_gz.instructions.items, .cc = cc, .align_inst = .none, // passed in the per-decl data @@ -3430,7 +3429,8 @@ fn testDecl( const func_inst = try decl_block.addFunc(.{ .src_node = node, .param_block = block_inst, - .ret_ty = .void_type, + .ret_ty = &.{}, + .ret_br = 0, .body = fn_block.instructions.items, .cc = .none, .align_inst = .none, @@ -9127,7 +9127,8 @@ const GenZir = struct { src_node: ast.Node.Index, body: []const Zir.Inst.Index, param_block: Zir.Inst.Index, - ret_ty: Zir.Inst.Ref, + ret_ty: []const Zir.Inst.Index, + ret_br: Zir.Inst.Index, cc: Zir.Inst.Ref, align_inst: Zir.Inst.Ref, lib_name: u32, @@ -9137,7 +9138,6 @@ const GenZir = struct { is_extern: bool, }) !Zir.Inst.Ref { assert(args.src_node != 0); - assert(args.ret_ty != .none); const astgen = gz.astgen; const gpa = astgen.gpa; @@ -9179,7 +9179,7 @@ const GenZir = struct { try astgen.extra.ensureUnusedCapacity( gpa, @typeInfo(Zir.Inst.ExtendedFunc).Struct.fields.len + - args.body.len + src_locs.len + + args.ret_ty.len + args.body.len + src_locs.len + @boolToInt(args.lib_name != 0) + @boolToInt(args.align_inst != .none) + @boolToInt(args.cc != .none), @@ -9187,7 +9187,7 @@ const GenZir = struct { const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{ .src_node = gz.nodeIndexToRelative(args.src_node), .param_block = args.param_block, - .return_type = args.ret_ty, + .ret_body_len = @intCast(u32, args.ret_ty.len), .body_len = @intCast(u32, args.body.len), }); if (args.lib_name != 0) { @@ -9199,10 +9199,14 @@ const GenZir = struct { if (args.align_inst != .none) { astgen.extra.appendAssumeCapacity(@enumToInt(args.align_inst)); } + astgen.extra.appendSliceAssumeCapacity(args.ret_ty); astgen.extra.appendSliceAssumeCapacity(args.body); astgen.extra.appendSliceAssumeCapacity(src_locs); const new_index = @intCast(Zir.Inst.Index, astgen.instructions.len); + if (args.ret_br != 0) { + astgen.instructions.items(.data)[args.ret_br].@"break".block_inst = new_index; + } astgen.instructions.appendAssumeCapacity(.{ .tag = .extended, .data = .{ .extended = .{ @@ -9222,23 +9226,27 @@ const GenZir = struct { gz.instructions.appendAssumeCapacity(new_index); return indexToRef(new_index); } else { - try gz.astgen.extra.ensureUnusedCapacity( + try astgen.extra.ensureUnusedCapacity( gpa, @typeInfo(Zir.Inst.Func).Struct.fields.len + - args.body.len + src_locs.len, + args.ret_ty.len + args.body.len + src_locs.len, ); - const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Func{ + const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.Func{ .param_block = args.param_block, - .return_type = args.ret_ty, + .ret_body_len = @intCast(u32, args.ret_ty.len), .body_len = @intCast(u32, args.body.len), }); - gz.astgen.extra.appendSliceAssumeCapacity(args.body); - gz.astgen.extra.appendSliceAssumeCapacity(src_locs); + astgen.extra.appendSliceAssumeCapacity(args.ret_ty); + astgen.extra.appendSliceAssumeCapacity(args.body); + astgen.extra.appendSliceAssumeCapacity(src_locs); const tag: Zir.Inst.Tag = if (args.is_inferred_error) .func_inferred else .func; - const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); - gz.astgen.instructions.appendAssumeCapacity(.{ + const new_index = @intCast(Zir.Inst.Index, astgen.instructions.len); + if (args.ret_br != 0) { + astgen.instructions.items(.data)[args.ret_br].@"break".block_inst = new_index; + } + astgen.instructions.appendAssumeCapacity(.{ .tag = tag, .data = .{ .pl_node = .{ .src_node = gz.nodeIndexToRelative(args.src_node), diff --git a/src/Module.zig b/src/Module.zig index 2556ad3f0e..da11bc1c3c 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -842,6 +842,9 @@ pub const Fn = struct { pub fn getInferredErrorSet(func: *Fn) ?*std.StringHashMapUnmanaged(void) { const ret_ty = func.owner_decl.ty.fnReturnType(); + if (ret_ty.tag() == .generic_poison) { + return null; + } if (ret_ty.zigTypeTag() == .ErrorUnion) { if (ret_ty.errorUnionSet().castTag(.error_set_inferred)) |payload| { return &payload.data.map; diff --git a/src/Sema.zig b/src/Sema.zig index 0557587fb5..73f22aa845 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2618,128 +2618,130 @@ fn analyzeCall( break :new_func gop.key_ptr.*; }; - try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); - - // Create a Decl for the new function. - const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node); - // TODO better names for generic function instantiations - const name_index = mod.getNextAnonNameIndex(); - new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ - module_fn.owner_decl.name, name_index, - }); - new_decl.src_line = module_fn.owner_decl.src_line; - new_decl.is_pub = module_fn.owner_decl.is_pub; - new_decl.is_exported = module_fn.owner_decl.is_exported; - new_decl.has_align = module_fn.owner_decl.has_align; - new_decl.has_linksection = module_fn.owner_decl.has_linksection; - new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; - new_decl.alive = true; // This Decl is called at runtime. - new_decl.has_tv = true; - new_decl.owns_tv = true; - new_decl.analysis = .in_progress; - new_decl.generation = mod.generation; - - namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); - - var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); - errdefer new_decl_arena.deinit(); - - // Re-run the block that creates the function, with the comptime parameters - // pre-populated inside `inst_map`. This causes `param_comptime` and - // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a - // new, monomorphized function, with the comptime parameters elided. - var child_sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = sema.arena, - .code = fn_zir, - .owner_decl = new_decl, - .namespace = namespace, - .func = null, - .owner_func = null, - .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), - .comptime_args_fn_inst = module_fn.zir_body_inst, - .preallocated_new_func = new_module_func, - }; - defer child_sema.deinit(); - - var child_block: Scope.Block = .{ - .parent = null, - .sema = &child_sema, - .src_decl = new_decl, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - }; - defer { - child_block.instructions.deinit(gpa); - child_block.params.deinit(gpa); - } - - try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); - var arg_i: usize = 0; - for (fn_info.param_body) |inst| { - const is_comptime = switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => false, - else => continue, - } or func_ty_info.paramIsComptime(arg_i); - const arg_src = call_src; // TODO: better source location - const arg = uncasted_args[arg_i]; - if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { - const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else if (is_comptime) { - return sema.failWithNeededComptime(block, arg_src); + { + try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); + + // Create a Decl for the new function. + const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node); + // TODO better names for generic function instantiations + const name_index = mod.getNextAnonNameIndex(); + new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ + module_fn.owner_decl.name, name_index, + }); + new_decl.src_line = module_fn.owner_decl.src_line; + new_decl.is_pub = module_fn.owner_decl.is_pub; + new_decl.is_exported = module_fn.owner_decl.is_exported; + new_decl.has_align = module_fn.owner_decl.has_align; + new_decl.has_linksection = module_fn.owner_decl.has_linksection; + new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; + new_decl.alive = true; // This Decl is called at runtime. + new_decl.has_tv = true; + new_decl.owns_tv = true; + new_decl.analysis = .in_progress; + new_decl.generation = mod.generation; + + namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + + // Re-run the block that creates the function, with the comptime parameters + // pre-populated inside `inst_map`. This causes `param_comptime` and + // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a + // new, monomorphized function, with the comptime parameters elided. + var child_sema: Sema = .{ + .mod = mod, + .gpa = gpa, + .arena = sema.arena, + .code = fn_zir, + .owner_decl = new_decl, + .namespace = namespace, + .func = null, + .owner_func = null, + .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), + .comptime_args_fn_inst = module_fn.zir_body_inst, + .preallocated_new_func = new_module_func, + }; + defer child_sema.deinit(); + + var child_block: Scope.Block = .{ + .parent = null, + .sema = &child_sema, + .src_decl = new_decl, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + }; + defer { + child_block.instructions.deinit(gpa); + child_block.params.deinit(gpa); } - arg_i += 1; - } - const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); - const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); - const new_func = new_func_val.castTag(.function).?.data; - assert(new_func == new_module_func); - arg_i = 0; - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, - else => continue, + try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + const is_comptime = switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime => true, + .param, .param_anytype => false, + else => continue, + } or func_ty_info.paramIsComptime(arg_i); + const arg_src = call_src; // TODO: better source location + const arg = uncasted_args[arg_i]; + if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { + const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else if (is_comptime) { + return sema.failWithNeededComptime(block, arg_src); + } + arg_i += 1; } - const arg = child_sema.inst_map.get(inst).?; - const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; + const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); + const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); + const new_func = new_func_val.castTag(.function).?.data; + assert(new_func == new_module_func); + + arg_i = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, + } + const arg = child_sema.inst_map.get(inst).?; + const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; - if (arg_val.tag() == .generic_poison) { - child_sema.comptime_args[arg_i] = .{ - .ty = Type.initTag(.noreturn), - .val = Value.initTag(.unreachable_value), - }; - } else { - child_sema.comptime_args[arg_i] = .{ - .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), - .val = try arg_val.copy(&new_decl_arena.allocator), - }; + if (arg_val.tag() == .generic_poison) { + child_sema.comptime_args[arg_i] = .{ + .ty = Type.initTag(.noreturn), + .val = Value.initTag(.unreachable_value), + }; + } else { + child_sema.comptime_args[arg_i] = .{ + .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), + .val = try arg_val.copy(&new_decl_arena.allocator), + }; + } + + arg_i += 1; } - arg_i += 1; - } + // Populate the Decl ty/val with the function and its type. + new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); + new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); + new_decl.analysis = .complete; - // Populate the Decl ty/val with the function and its type. - new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); - new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); - new_decl.analysis = .complete; + // The generic function Decl is guaranteed to be the first dependency + // of each of its instantiations. + assert(new_decl.dependencies.keys().len == 0); + try mod.declareDeclDependency(new_decl, module_fn.owner_decl); - // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field - // will be populated, ensuring it will have `analyzeBody` called with the ZIR - // parameters mapped appropriately. - try mod.comp.bin_file.allocateDeclIndexes(new_decl); - try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); + // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field + // will be populated, ensuring it will have `analyzeBody` called with the ZIR + // parameters mapped appropriately. + try mod.comp.bin_file.allocateDeclIndexes(new_decl); + try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); - try new_decl.finalizeNewArena(&new_decl_arena); - - // The generic function Decl is guaranteed to be the first dependency - // of each of its instantiations. - assert(new_decl.dependencies.keys().len == 0); - try mod.declareDeclDependency(new_decl, module_fn.owner_decl); + try new_decl.finalizeNewArena(&new_decl_arena); + } break :res try sema.finishGenericCall( block, @@ -3478,12 +3480,15 @@ fn zirFunc( const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index); + var extra_index = extra.end; + const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; var body_inst: Zir.Inst.Index = 0; var src_locs: Zir.Inst.Func.SrcLocs = undefined; if (extra.data.body_len != 0) { body_inst = inst; - const extra_index = extra.end + extra.data.body_len; + extra_index += extra.data.body_len; src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } @@ -3496,7 +3501,7 @@ fn zirFunc( block, inst_data.src_node, body_inst, - extra.data.return_type, + ret_ty_body, cc, Value.initTag(.null_value), false, @@ -3512,7 +3517,7 @@ fn funcCommon( block: *Scope.Block, src_node_offset: i32, body_inst: Zir.Inst.Index, - zir_return_type: Zir.Inst.Ref, + ret_ty_body: []const Zir.Inst.Index, cc: std.builtin.CallingConvention, align_val: Value, var_args: bool, @@ -3523,7 +3528,37 @@ fn funcCommon( ) CompileError!Air.Inst.Ref { const src: LazySrcLoc = .{ .node_offset = src_node_offset }; const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset }; - const bare_return_type = try sema.resolveType(block, ret_ty_src, zir_return_type); + + // The return type body might be a type expression that depends on generic parameters. + // In such case we need to use a generic_poison value for the return type and mark + // the function as generic. + var is_generic = false; + const bare_return_type: Type = ret_ty: { + if (ret_ty_body.len == 0) break :ret_ty Type.initTag(.void); + + const err = err: { + // Make sure any nested param instructions don't clobber our work. + const prev_params = block.params; + block.params = .{}; + defer { + block.params.deinit(sema.gpa); + block.params = prev_params; + } + if (sema.resolveBody(block, ret_ty_body)) |ret_ty_inst| { + if (sema.analyzeAsType(block, ret_ty_src, ret_ty_inst)) |ret_ty| { + break :ret_ty ret_ty; + } else |err| break :err err; + } else |err| break :err err; + }; + switch (err) { + error.GenericPoison => { + // The type is not available until the generic instantiation. + is_generic = true; + break :ret_ty Type.initTag(.generic_poison); + }, + else => |e| return e, + } + }; const mod = sema.mod; @@ -3540,8 +3575,9 @@ fn funcCommon( const fn_ty: Type = fn_ty: { // Hot path for some common function types. - if (block.params.items.len == 0 and !var_args and align_val.tag() == .null_value and - !inferred_error_set) + // TODO can we eliminate some of these Type tag values? seems unnecessarily complicated. + if (!is_generic and block.params.items.len == 0 and !var_args and + align_val.tag() == .null_value and !inferred_error_set) { if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) { break :fn_ty Type.initTag(.fn_noreturn_no_args); @@ -3560,7 +3596,6 @@ fn funcCommon( } } - var is_generic = false; const param_types = try sema.arena.alloc(Type, block.params.items.len); const comptime_params = try sema.arena.alloc(bool, block.params.items.len); for (block.params.items) |param, i| { @@ -3574,7 +3609,9 @@ fn funcCommon( return mod.fail(&block.base, src, "TODO implement support for function prototypes to have alignment specified", .{}); } - const return_type = if (!inferred_error_set) bare_return_type else blk: { + const return_type = if (!inferred_error_set or bare_return_type.tag() == .generic_poison) + bare_return_type + else blk: { const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, .{ .func = new_func, .map = .{}, @@ -6944,6 +6981,9 @@ fn zirFuncExtended( break :blk align_tv.val; } else Value.initTag(.null_value); + const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + var body_inst: Zir.Inst.Index = 0; var src_locs: Zir.Inst.Func.SrcLocs = undefined; if (extra.data.body_len != 0) { @@ -6960,7 +7000,7 @@ fn zirFuncExtended( block, extra.data.src_node, body_inst, - extra.data.return_type, + ret_ty_body, cc, align_val, is_var_args, diff --git a/src/Zir.zig b/src/Zir.zig index 862af7b033..094cef3393 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2272,11 +2272,13 @@ pub const Inst = struct { /// 0. lib_name: u32, // null terminated string index, if has_lib_name is set /// 1. cc: Ref, // if has_cc is set /// 2. align: Ref, // if has_align is set - /// 3. body: Index // for each body_len - /// 4. src_locs: Func.SrcLocs // if body_len != 0 + /// 3. return_type: Index // for each ret_body_len + /// 4. body: Index // for each body_len + /// 5. src_locs: Func.SrcLocs // if body_len != 0 pub const ExtendedFunc = struct { src_node: i32, - return_type: Ref, + /// If this is 0 it means a void return type. + ret_body_len: u32, /// Points to the block that contains the param instructions for this function. param_block: Index, body_len: u32, @@ -2312,10 +2314,12 @@ pub const Inst = struct { }; /// Trailing: - /// 0. body: Index // for each body_len - /// 1. src_locs: SrcLocs // if body_len != 0 + /// 0. return_type: Index // for each ret_body_len + /// 1. body: Index // for each body_len + /// 2. src_locs: SrcLocs // if body_len != 0 pub const Func = struct { - return_type: Ref, + /// If this is 0 it means a void return type. + ret_body_len: u32, /// Points to the block that contains the param instructions for this function. param_block: Index, body_len: u32, @@ -4344,15 +4348,21 @@ const Writer = struct { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const extra = self.code.extraData(Inst.Func, inst_data.payload_index); - const body = self.code.extra[extra.end..][0..extra.data.body_len]; + var extra_index = extra.end; + + const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + + const body = self.code.extra[extra_index..][0..extra.data.body_len]; + extra_index += body.len; + var src_locs: Zir.Inst.Func.SrcLocs = undefined; if (body.len != 0) { - const extra_index = extra.end + body.len; src_locs = self.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } return self.writeFuncCommon( stream, - extra.data.return_type, + ret_ty_body, inferred_error_set, false, false, @@ -4387,6 +4397,9 @@ const Writer = struct { break :blk align_inst; }; + const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + const body = self.code.extra[extra_index..][0..extra.data.body_len]; extra_index += body.len; @@ -4396,7 +4409,7 @@ const Writer = struct { } return self.writeFuncCommon( stream, - extra.data.return_type, + ret_ty_body, small.is_inferred_error, small.is_var_args, small.is_extern, @@ -4478,7 +4491,7 @@ const Writer = struct { fn writeFuncCommon( self: *Writer, stream: anytype, - ret_ty: Inst.Ref, + ret_ty_body: []const Inst.Index, inferred_error_set: bool, var_args: bool, is_extern: bool, @@ -4488,7 +4501,13 @@ const Writer = struct { src: LazySrcLoc, src_locs: Zir.Inst.Func.SrcLocs, ) !void { - try self.writeInstRef(stream, ret_ty); + try stream.writeAll("ret_ty={\n"); + self.indent += 2; + try self.writeBody(stream, ret_ty_body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + try self.writeOptionalInstRef(stream, ", cc=", cc); try self.writeOptionalInstRef(stream, ", align=", align_inst); try self.writeFlag(stream, ", vargs", var_args); @@ -4496,9 +4515,9 @@ const Writer = struct { try self.writeFlag(stream, ", inferror", inferred_error_set); if (body.len == 0) { - try stream.writeAll(", {}) "); + try stream.writeAll(", body={}) "); } else { - try stream.writeAll(", {\n"); + try stream.writeAll(", body={\n"); self.indent += 2; try self.writeBody(stream, body); self.indent -= 2; @@ -4932,6 +4951,7 @@ fn findDeclsBody( pub const FnInfo = struct { param_body: []const Inst.Index, + ret_ty_body: []const Inst.Index, body: []const Inst.Index, total_params_len: u32, }; @@ -4942,13 +4962,22 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { const info: struct { param_block: Inst.Index, body: []const Inst.Index, + ret_ty_body: []const Inst.Index, } = switch (tags[fn_inst]) { .func, .func_inferred => blk: { const inst_data = datas[fn_inst].pl_node; const extra = zir.extraData(Inst.Func, inst_data.payload_index); - const body = zir.extra[extra.end..][0..extra.data.body_len]; + var extra_index: usize = extra.end; + + const ret_ty_body = zir.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + + const body = zir.extra[extra_index..][0..extra.data.body_len]; + extra_index += body.len; + break :blk .{ .param_block = extra.data.param_block, + .ret_ty_body = ret_ty_body, .body = body, }; }, @@ -4961,9 +4990,13 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { extra_index += @boolToInt(small.has_lib_name); extra_index += @boolToInt(small.has_cc); extra_index += @boolToInt(small.has_align); + const ret_ty_body = zir.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; const body = zir.extra[extra_index..][0..extra.data.body_len]; + extra_index += body.len; break :blk .{ .param_block = extra.data.param_block, + .ret_ty_body = ret_ty_body, .body = body, }; }, @@ -4983,6 +5016,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { } return .{ .param_body = param_body, + .ret_ty_body = info.ret_ty_body, .body = info.body, .total_params_len = total_params_len, }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0e73469687..91b48ffcfb 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -575,6 +575,14 @@ pub const DeclGen = struct { const info = t.intInfo(self.module.getTarget()); return self.context.intType(info.bits); }, + .Float => switch (t.floatBits(self.module.getTarget())) { + 16 => return self.context.halfType(), + 32 => return self.context.floatType(), + 64 => return self.context.doubleType(), + 80 => return self.context.x86FP80Type(), + 128 => return self.context.fp128Type(), + else => unreachable, + }, .Bool => return self.context.intType(1), .Pointer => { if (t.isSlice()) { @@ -661,7 +669,6 @@ pub const DeclGen = struct { .BoundFn => @panic("TODO remove BoundFn from the language"), - .Float, .Enum, .Union, .Opaque, @@ -699,6 +706,13 @@ pub const DeclGen = struct { } return llvm_int; }, + .Float => { + if (tv.ty.floatBits(self.module.getTarget()) <= 64) { + const llvm_ty = try self.llvmType(tv.ty); + return llvm_ty.constReal(tv.val.toFloat(f64)); + } + return self.todo("bitcast to f128 from an integer", .{}); + }, .Pointer => switch (tv.val.tag()) { .decl_ref => { if (tv.ty.isSlice()) { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 4af3cadd84..675c5539fb 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -31,6 +31,21 @@ pub const Context = opaque { pub const intType = LLVMIntTypeInContext; extern fn LLVMIntTypeInContext(C: *const Context, NumBits: c_uint) *const Type; + pub const halfType = LLVMHalfTypeInContext; + extern fn LLVMHalfTypeInContext(C: *const Context) *const Type; + + pub const floatType = LLVMFloatTypeInContext; + extern fn LLVMFloatTypeInContext(C: *const Context) *const Type; + + pub const doubleType = LLVMDoubleTypeInContext; + extern fn LLVMDoubleTypeInContext(C: *const Context) *const Type; + + pub const x86FP80Type = LLVMX86FP80TypeInContext; + extern fn LLVMX86FP80TypeInContext(C: *const Context) *const Type; + + pub const fp128Type = LLVMFP128TypeInContext; + extern fn LLVMFP128TypeInContext(C: *const Context) *const Type; + pub const voidType = LLVMVoidTypeInContext; extern fn LLVMVoidTypeInContext(C: *const Context) *const Type; @@ -127,6 +142,9 @@ pub const Type = opaque { pub const constInt = LLVMConstInt; extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value; + pub const constReal = LLVMConstReal; + extern fn LLVMConstReal(RealTy: *const Type, N: f64) *const Value; + pub const constArray = LLVMConstArray; extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: [*]*const Value, Length: c_uint) *const Value; diff --git a/src/print_air.zig b/src/print_air.zig index 00317b26e8..11f2982fc3 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -222,7 +222,7 @@ const Writer = struct { const extra = w.air.extraData(Air.Block, ty_pl.payload); const body = w.air.extra[extra.end..][0..extra.data.body_len]; - try s.writeAll("{\n"); + try s.print("{}, {{\n", .{w.air.getRefType(ty_pl.ty)}); const old_indent = w.indent; w.indent += 2; try w.writeBody(s, body); diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index e3fed907df..a3c5668b39 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; @@ -14,3 +15,58 @@ test "one param, explicit comptime" { fn checkSize(comptime T: type) usize { return @sizeOf(T); } + +test "simple generic fn" { + try expect(max(i32, 3, -1) == 3); + try expect(max(u8, 1, 100) == 100); + if (!builtin.zig_is_stage2) { + // TODO: stage2 is incorrectly emitting the following: + // error: cast of value 1.23e-01 to type 'f32' loses information + try expect(max(f32, 0.123, 0.456) == 0.456); + } + try expect(add(2, 3) == 5); +} + +fn max(comptime T: type, a: T, b: T) T { + if (!builtin.zig_is_stage2) { + // TODO: stage2 is incorrectly emitting AIR that allocates a result + // value, stores to it, but then returns void instead of the result. + return if (a > b) a else b; + } + if (a > b) { + return a; + } else { + return b; + } +} + +fn add(comptime a: i32, b: i32) i32 { + return (comptime a) + b; +} + +const the_max = max(u32, 1234, 5678); +test "compile time generic eval" { + try expect(the_max == 5678); +} + +fn gimmeTheBigOne(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn shouldCallSameInstance(a: u32, b: u32) u32 { + return max(u32, a, b); +} + +fn sameButWithFloats(a: f64, b: f64) f64 { + return max(f64, a, b); +} + +test "fn with comptime args" { + try expect(gimmeTheBigOne(1234, 5678) == 5678); + try expect(shouldCallSameInstance(34, 12) == 34); + if (!builtin.zig_is_stage2) { + // TODO: stage2 llvm backend needs to use fcmp instead of icmp + // probably AIR should just have different instructions for floats. + try expect(sameButWithFloats(0.43, 0.49) == 0.49); + } +} diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig index 104752607a..c9f274c7c1 100644 --- a/test/behavior/generics_stage1.zig +++ b/test/behavior/generics_stage1.zig @@ -3,44 +3,7 @@ const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; -test "simple generic fn" { - try expect(max(i32, 3, -1) == 3); - try expect(max(f32, 0.123, 0.456) == 0.456); - try expect(add(2, 3) == 5); -} - -fn max(comptime T: type, a: T, b: T) T { - return if (a > b) a else b; -} - -fn add(comptime a: i32, b: i32) i32 { - return (comptime a) + b; -} - -const the_max = max(u32, 1234, 5678); -test "compile time generic eval" { - try expect(the_max == 5678); -} - -fn gimmeTheBigOne(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn shouldCallSameInstance(a: u32, b: u32) u32 { - return max(u32, a, b); -} - -fn sameButWithFloats(a: f64, b: f64) f64 { - return max(f64, a, b); -} - -test "fn with comptime args" { - try expect(gimmeTheBigOne(1234, 5678) == 5678); - try expect(shouldCallSameInstance(34, 12) == 34); - try expect(sameButWithFloats(0.43, 0.49) == 0.49); -} - -test "var params" { +test "anytype params" { try expect(max_i32(12, 34) == 34); try expect(max_f64(1.2, 3.4) == 3.4); } -- cgit v1.2.3 From c7dc451a2a06a0ade0bb44a48cb6e5cde6e237df Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Aug 2021 23:20:53 -0700 Subject: stage2: more debuggable panics For now these errors are handled via `@panic` rather than `unreachable`. These are relatively likely bugs to occur at this early stage of development, and handling them as panics lets us ship release builds of the compiler without worrying about undefined behavior. Furthermore, in stage1, `@panic` is implemented to include an error return trace, while `unreachable` is not. In this case, the error return traces are extremely helpful in debugging the compiler. --- src/Module.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index da11bc1c3c..a694f775da 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3792,8 +3792,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { log.debug("set {s} to in_progress", .{decl.name}); _ = sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) { - error.NeededSourceLocation => unreachable, - error.GenericPoison => unreachable, + error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"), + error.GenericPoison => @panic("zig compiler bug: GenericPoison"), else => |e| return e, }; -- cgit v1.2.3 From ede76f4fe31e6fc935ae10f030eb2c97ed36aaaa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Aug 2021 16:24:39 -0700 Subject: stage2: fix generics with non-comptime anytype parameters The `comptime_args` field of Fn has a clarified purpose: For generic function instantiations, there is a `TypedValue` here for each parameter of the function: * Non-comptime parameters are marked with a `generic_poison` for the value. * Non-anytype parameters are marked with a `generic_poison` for the type. Sema now has a `fn_ret_ty` field. Doc comments reproduced here: > When semantic analysis needs to know the return type of the function whose body > is being analyzed, this `Type` should be used instead of going through `func`. > This will correctly handle the case of a comptime/inline function call of a > generic function which uses a type expression for the return type. > The type will be `void` in the case that `func` is `null`. Various places in Sema are modified in accordance with this guidance. Fixed `resolveMaybeUndefVal` not returning `error.GenericPoison` when Value Tag of `generic_poison` is encountered. Fixed generic function memoization incorrect equality checking. The logic now clearly deals properly with any combination of anytype and comptime parameters. Fixed not removing generic function instantiation from the table in case a compile errors in the rest of `call` semantic analysis. This required introduction of yet another adapter which I have called `GenericRemoveAdapter`. This one is nice and simple - it's the same hash function (the same precomputed hash is passed in) but the equality function checks pointers rather than doing any logic. Inline/comptime function calls coerce each argument in accordance with the function parameter type expressions. Likewise the return type expression is evaluated and provided (see `fn_ret_ty` above). There's a new compile error "unable to monomorphize function". It's pretty unhelpful and will need to get improved in the future. It happens when a type expression in a generic function did not end up getting resolved at a callsite. This can happen, for example, if a runtime parameter is attempted to be used where it needed to be comptime known: ```zig fn foo(x: anytype) [x]u8 { _ = x; } ``` In this example, even if we pass a number such as `10` for `x`, it is not marked `comptime`, so `x` will have a runtime known value, making the return type unable to resolve. In the LLVM backend I implement cmp instructions for float types to pass some behavior tests that used floats. --- src/Module.zig | 12 +- src/Sema.zig | 319 +++++++++++++++++++++++++------------- src/codegen/llvm.zig | 41 +++-- src/codegen/llvm/bindings.zig | 22 ++- test/behavior/generics.zig | 38 ++++- test/behavior/generics_stage1.zig | 22 --- 6 files changed, 303 insertions(+), 151 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index a694f775da..3652a27927 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -801,8 +801,9 @@ pub const Fn = struct { /// The Decl that corresponds to the function itself. owner_decl: *Decl, /// If this is not null, this function is a generic function instantiation, and - /// there is a `Value` here for each parameter of the function. Non-comptime - /// parameters are marked with an `unreachable_value`. + /// there is a `TypedValue` here for each parameter of the function. + /// Non-comptime parameters are marked with a `generic_poison` for the value. + /// Non-anytype parameters are marked with a `generic_poison` for the type. comptime_args: ?[*]TypedValue = null, /// The ZIR instruction that is a function instruction. Use this to find /// the body. We store this rather than the body directly so that when ZIR @@ -2975,6 +2976,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { .owner_decl = new_decl, .namespace = &struct_obj.namespace, .func = null, + .fn_ret_ty = Type.initTag(.void), .owner_func = null, }; defer sema.deinit(); @@ -3029,6 +3031,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { .owner_decl = decl, .namespace = decl.namespace, .func = null, + .fn_ret_ty = Type.initTag(.void), .owner_func = null, }; defer sema.deinit(); @@ -3712,6 +3715,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { .owner_decl = decl, .namespace = decl.namespace, .func = func, + .fn_ret_ty = func.owner_decl.ty.fnReturnType(), .owner_func = func, }; defer sema.deinit(); @@ -3764,7 +3768,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { }; if (func.comptime_args) |comptime_args| { const arg_tv = comptime_args[total_param_index]; - if (arg_tv.val.tag() != .unreachable_value) { + if (arg_tv.val.tag() != .generic_poison) { // We have a comptime value for this parameter. const arg = try sema.addConstant(arg_tv.ty, arg_tv.val); sema.inst_map.putAssumeCapacityNoClobber(inst, arg); @@ -4447,6 +4451,7 @@ pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void .namespace = &struct_obj.namespace, .owner_func = null, .func = null, + .fn_ret_ty = Type.initTag(.void), }; defer sema.deinit(); @@ -4600,6 +4605,7 @@ pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void { .namespace = &union_obj.namespace, .owner_func = null, .func = null, + .fn_ret_ty = Type.initTag(.void), }; defer sema.deinit(); diff --git a/src/Sema.zig b/src/Sema.zig index 825737e4c5..8bf4111566 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -29,6 +29,12 @@ owner_func: ?*Module.Fn, /// This starts out the same as `owner_func` and then diverges in the case of /// an inline or comptime function call. func: ?*Module.Fn, +/// When semantic analysis needs to know the return type of the function whose body +/// is being analyzed, this `Type` should be used instead of going through `func`. +/// This will correctly handle the case of a comptime/inline function call of a +/// generic function which uses a type expression for the return type. +/// The type will be `void` in the case that `func` is `null`. +fn_ret_ty: Type, branch_quota: u32 = 1000, branch_count: u32 = 0, /// This field is updated when a new source location becomes active, so that @@ -628,6 +634,7 @@ fn analyzeAsType( /// May return Value Tags: `variable`, `undef`. /// See `resolveConstValue` for an alternative. +/// Value Tag `generic_poison` causes `error.GenericPoison` to be returned. fn resolveValue( sema: *Sema, block: *Scope.Block, @@ -679,6 +686,7 @@ fn resolveDefinedValue( /// Value Tag `variable` causes this function to return `null`. /// Value Tag `undef` causes this function to return the Value. +/// Value Tag `generic_poison` causes `error.GenericPoison` to be returned. fn resolveMaybeUndefVal( sema: *Sema, block: *Scope.Block, @@ -686,10 +694,11 @@ fn resolveMaybeUndefVal( inst: Air.Inst.Ref, ) CompileError!?Value { const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; - if (val.tag() == .variable) { - return null; + switch (val.tag()) { + .variable => return null, + .generic_poison => return error.GenericPoison, + else => return val, } - return val; } /// Returns all Value tags including `variable` and `undef`. @@ -1033,6 +1042,7 @@ fn zirEnumDecl( .namespace = &enum_obj.namespace, .owner_func = null, .func = null, + .fn_ret_ty = Type.initTag(.void), .branch_quota = sema.branch_quota, .branch_count = sema.branch_count, }; @@ -1238,9 +1248,7 @@ fn zirRetPtr( const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; try sema.requireFunctionBlock(block, src); - const fn_ty = sema.func.?.owner_decl.ty; - const ret_type = fn_ty.fnReturnType(); - const ptr_type = try Module.simplePtrType(sema.arena, ret_type, true, .One); + const ptr_type = try Module.simplePtrType(sema.arena, sema.fn_ret_ty, true, .One); return block.addTy(.alloc, ptr_type); } @@ -1263,9 +1271,7 @@ fn zirRetType( const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; try sema.requireFunctionBlock(block, src); - const fn_ty = sema.func.?.owner_decl.ty; - const ret_type = fn_ty.fnReturnType(); - return sema.addType(ret_type); + return sema.addType(sema.fn_ret_ty); } fn zirEnsureResultUsed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { @@ -2364,7 +2370,7 @@ const GenericCallAdapter = struct { generic_fn: *Module.Fn, precomputed_hash: u64, func_ty_info: Type.Payload.Function.Data, - comptime_vals: []const Value, + comptime_tvs: []const TypedValue, pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool { _ = adapted_key; @@ -2373,12 +2379,22 @@ const GenericCallAdapter = struct { const generic_owner_decl = other_key.owner_decl.dependencies.keys()[0]; if (ctx.generic_fn.owner_decl != generic_owner_decl) return false; - // This logic must be kept in sync with the logic in `analyzeCall` that - // computes the hash. const other_comptime_args = other_key.comptime_args.?; - for (ctx.func_ty_info.param_types) |param_ty, i| { - if (ctx.func_ty_info.paramIsComptime(i) and param_ty.tag() != .generic_poison) { - if (!ctx.comptime_vals[i].eql(other_comptime_args[i].val, param_ty)) { + for (other_comptime_args[0..ctx.func_ty_info.param_types.len]) |other_arg, i| { + if (other_arg.ty.tag() != .generic_poison) { + // anytype parameter + if (!other_arg.ty.eql(ctx.comptime_tvs[i].ty)) { + return false; + } + } + if (other_arg.val.tag() != .generic_poison) { + // comptime parameter + if (ctx.comptime_tvs[i].val.tag() == .generic_poison) { + // No match because the instantiation has a comptime parameter + // but the callsite does not. + return false; + } + if (!other_arg.val.eql(ctx.comptime_tvs[i].val, other_arg.ty)) { return false; } } @@ -2394,6 +2410,22 @@ const GenericCallAdapter = struct { } }; +const GenericRemoveAdapter = struct { + precomputed_hash: u64, + + pub fn eql(ctx: @This(), adapted_key: *Module.Fn, other_key: *Module.Fn) bool { + _ = ctx; + return adapted_key == other_key; + } + + /// The implementation of the hash is in semantic analysis of function calls, so + /// that any errors when computing the hash can be properly reported. + pub fn hash(ctx: @This(), adapted_key: *Module.Fn) u64 { + _ = adapted_key; + return ctx.precomputed_hash; + } +}; + fn analyzeCall( sema: *Sema, block: *Scope.Block, @@ -2466,14 +2498,6 @@ fn analyzeCall( const is_inline_call = is_comptime_call or modifier == .always_inline or func_ty_info.cc == .Inline; const result: Air.Inst.Ref = if (is_inline_call) res: { - // TODO look into not allocating this args array - const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); - for (uncasted_args) |uncasted_arg, i| { - const param_ty = func_ty.fnParamType(i); - const arg_src = call_src; // TODO: better source location - args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src); - } - const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = switch (func_val.tag()) { .function => func_val.castTag(.function).?.data, @@ -2544,19 +2568,62 @@ fn analyzeCall( // This will have return instructions analyzed as break instructions to // the block_inst above. Here we are performing "comptime/inline semantic analysis" // for a function body, which means we must map the parameter ZIR instructions to - // the AIR instructions of the callsite. + // the AIR instructions of the callsite. The callee could be a generic function + // which means its parameter type expressions must be resolved in order and used + // to successively coerce the arguments. const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst); const zir_tags = sema.code.instructions.items(.tag); var arg_i: usize = 0; - try sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, args.len)); - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param, .param_comptime, .param_anytype, .param_anytype_comptime => {}, - else => continue, + for (fn_info.param_body) |inst| switch (zir_tags[inst]) { + .param, .param_comptime => { + // Evaluate the parameter type expression now that previous ones have + // been mapped, and coerce the corresponding argument to it. + const pl_tok = sema.code.instructions.items(.data)[inst].pl_tok; + const param_src = pl_tok.src(); + const extra = sema.code.extraData(Zir.Inst.Param, pl_tok.payload_index); + const param_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + const param_ty_inst = try sema.resolveBody(&child_block, param_body); + const param_ty = try sema.analyzeAsType(&child_block, param_src, param_ty_inst); + const arg_src = call_src; // TODO: better source location + const casted_arg = try sema.coerce(&child_block, param_ty, uncasted_args[arg_i], arg_src); + try sema.inst_map.putNoClobber(gpa, inst, casted_arg); + arg_i += 1; + continue; + }, + .param_anytype, .param_anytype_comptime => { + // No coercion needed. + try sema.inst_map.putNoClobber(gpa, inst, uncasted_args[arg_i]); + arg_i += 1; + continue; + }, + else => continue, + }; + + // In case it is a generic function with an expression for the return type that depends + // on parameters, we must now do the same for the return type as we just did with + // each of the parameters, resolving the return type and providing it to the child + // `Sema` so that it can be used for the `ret_ptr` instruction. + const ret_ty_inst = try sema.resolveBody(&child_block, fn_info.ret_ty_body); + const ret_ty_src = func_src; // TODO better source location + const bare_return_type = try sema.analyzeAsType(&child_block, ret_ty_src, ret_ty_inst); + // If the function has an inferred error set, `bare_return_type` is the payload type only. + const fn_ret_ty = blk: { + // TODO instead of reusing the function's inferred error set, this code should + // create a temporary error set which is used for the comptime/inline function + // call alone, independent from the runtime instantiation. + if (func_ty_info.return_type.castTag(.error_union)) |payload| { + const error_set_ty = payload.data.error_set; + break :blk try Type.Tag.error_union.create(sema.arena, .{ + .error_set = error_set_ty, + .payload = bare_return_type, + }); } - sema.inst_map.putAssumeCapacityNoClobber(inst, args[arg_i]); - arg_i += 1; - } + break :blk bare_return_type; + }; + const parent_fn_ret_ty = sema.fn_ret_ty; + sema.fn_ret_ty = fn_ret_ty; + defer sema.fn_ret_ty = parent_fn_ret_ty; + _ = try sema.analyzeBody(&child_block, fn_info.body); break :res try sema.analyzeBlockBody(block, call_src, &child_block, merges); } else if (func_ty_info.is_generic) res: { @@ -2569,57 +2636,74 @@ fn analyzeCall( const fn_zir = namespace.file_scope.zir; const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst); const zir_tags = fn_zir.instructions.items(.tag); - const new_module_func = new_func: { - // This hash must match `Module.MonomorphedFuncsContext.hash`. - // For parameters explicitly marked comptime and simple parameter type expressions, - // we know whether a parameter is elided from a monomorphed function, and can - // use it in the hash here. However, for parameter type expressions that are not - // explicitly marked comptime and rely on previous parameter comptime values, we - // don't find out until after generating a monomorphed function whether the parameter - // type ended up being a "must-be-comptime-known" type. - var hasher = std.hash.Wyhash.init(0); - std.hash.autoHash(&hasher, @ptrToInt(module_fn)); - - const comptime_vals = try sema.arena.alloc(Value, func_ty_info.param_types.len); - - for (func_ty_info.param_types) |param_ty, i| { - const is_comptime = func_ty_info.paramIsComptime(i); - if (is_comptime and param_ty.tag() != .generic_poison) { - const arg_src = call_src; // TODO better source location - const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src); - if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| { + + // This hash must match `Module.MonomorphedFuncsContext.hash`. + // For parameters explicitly marked comptime and simple parameter type expressions, + // we know whether a parameter is elided from a monomorphed function, and can + // use it in the hash here. However, for parameter type expressions that are not + // explicitly marked comptime and rely on previous parameter comptime values, we + // don't find out until after generating a monomorphed function whether the parameter + // type ended up being a "must-be-comptime-known" type. + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, @ptrToInt(module_fn)); + + const comptime_tvs = try sema.arena.alloc(TypedValue, func_ty_info.param_types.len); + + for (func_ty_info.param_types) |param_ty, i| { + const is_comptime = func_ty_info.paramIsComptime(i); + if (is_comptime) { + const arg_src = call_src; // TODO better source location + const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src); + if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| { + if (param_ty.tag() != .generic_poison) { arg_val.hash(param_ty, &hasher); - comptime_vals[i] = arg_val; - } else { - return sema.failWithNeededComptime(block, arg_src); } + comptime_tvs[i] = .{ + // This will be different than `param_ty` in the case of `generic_poison`. + .ty = sema.typeOf(casted_arg), + .val = arg_val, + }; + } else { + return sema.failWithNeededComptime(block, arg_src); } + } else { + comptime_tvs[i] = .{ + .ty = sema.typeOf(uncasted_args[i]), + .val = Value.initTag(.generic_poison), + }; } + } - const adapter: GenericCallAdapter = .{ - .generic_fn = module_fn, - .precomputed_hash = hasher.final(), - .func_ty_info = func_ty_info, - .comptime_vals = comptime_vals, - }; - const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); - if (gop.found_existing) { - const callee_func = gop.key_ptr.*; - break :res try sema.finishGenericCall( - block, - call_src, - callee_func, - func_src, - uncasted_args, - fn_info, - zir_tags, - ); - } - gop.key_ptr.* = try gpa.create(Module.Fn); - break :new_func gop.key_ptr.*; - }; + const precomputed_hash = hasher.final(); + const adapter: GenericCallAdapter = .{ + .generic_fn = module_fn, + .precomputed_hash = precomputed_hash, + .func_ty_info = func_ty_info, + .comptime_tvs = comptime_tvs, + }; + const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); + if (gop.found_existing) { + const callee_func = gop.key_ptr.*; + break :res try sema.finishGenericCall( + block, + call_src, + callee_func, + func_src, + uncasted_args, + fn_info, + zir_tags, + ); + } + const new_module_func = try gpa.create(Module.Fn); + gop.key_ptr.* = new_module_func; { + errdefer gpa.destroy(new_module_func); + const remove_adapter: GenericRemoveAdapter = .{ + .precomputed_hash = precomputed_hash, + }; + errdefer assert(mod.monomorphed_funcs.removeAdapted(new_module_func, remove_adapter)); + try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); // Create a Decl for the new function. @@ -2658,6 +2742,7 @@ fn analyzeCall( .owner_decl = new_decl, .namespace = namespace, .func = null, + .fn_ret_ty = Type.initTag(.void), .owner_func = null, .comptime_args = try new_decl_arena.allocator.alloc(TypedValue, uncasted_args.len), .comptime_args_fn_inst = module_fn.zir_body_inst, @@ -2681,11 +2766,25 @@ fn analyzeCall( try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); var arg_i: usize = 0; for (fn_info.param_body) |inst| { - const is_comptime = switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime => true, - .param, .param_anytype => false, + var is_comptime = false; + var is_anytype = false; + switch (zir_tags[inst]) { + .param => { + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_comptime => { + is_comptime = true; + }, + .param_anytype => { + is_anytype = true; + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_anytype_comptime => { + is_anytype = true; + is_comptime = true; + }, else => continue, - } or func_ty_info.paramIsComptime(arg_i); + } const arg_src = call_src; // TODO: better source location const arg = uncasted_args[arg_i]; if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { @@ -2693,6 +2792,12 @@ fn analyzeCall( child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); } else if (is_comptime) { return sema.failWithNeededComptime(block, arg_src); + } else if (is_anytype) { + const child_arg = try child_sema.addConstant( + sema.typeOf(arg), + Value.initTag(.generic_poison), + ); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); } arg_i += 1; } @@ -2710,17 +2815,10 @@ fn analyzeCall( const arg = child_sema.inst_map.get(inst).?; const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; - if (arg_val.tag() == .generic_poison) { - child_sema.comptime_args[arg_i] = .{ - .ty = Type.initTag(.noreturn), - .val = Value.initTag(.unreachable_value), - }; - } else { - child_sema.comptime_args[arg_i] = .{ - .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), - .val = try arg_val.copy(&new_decl_arena.allocator), - }; - } + child_sema.comptime_args[arg_i] = .{ + .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), + .val = try arg_val.copy(&new_decl_arena.allocator), + }; arg_i += 1; } @@ -2730,6 +2828,18 @@ fn analyzeCall( new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); new_decl.analysis = .complete; + if (new_decl.ty.fnInfo().is_generic) { + // TODO improve this error message. This can happen because of the parameter + // type expression or return type expression depending on runtime-provided values. + // The error message should be emitted in zirParam or funcCommon when it + // is determined that we are trying to instantiate a generic function. + return mod.fail(&block.base, call_src, "unable to monomorphize function", .{}); + } + + log.debug("generic function '{s}' instantiated with type {}", .{ + new_decl.name, new_decl.ty, + }); + // The generic function Decl is guaranteed to be the first dependency // of each of its instantiations. assert(new_decl.dependencies.keys().len == 0); @@ -2809,7 +2919,7 @@ fn finishGenericCall( for (fn_info.param_body) |inst| { switch (zir_tags[inst]) { .param_comptime, .param_anytype_comptime, .param, .param_anytype => { - if (comptime_args[arg_i].val.tag() == .unreachable_value) { + if (comptime_args[arg_i].val.tag() == .generic_poison) { count += 1; } arg_i += 1; @@ -2829,7 +2939,7 @@ fn finishGenericCall( .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, else => continue, } - const is_runtime = comptime_args[total_i].val.tag() == .unreachable_value; + const is_runtime = comptime_args[total_i].val.tag() == .generic_poison; if (is_runtime) { const param_ty = new_fn_ty.fnParamType(runtime_i); const arg_src = call_src; // TODO: better source location @@ -6162,28 +6272,23 @@ fn zirRetNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr fn analyzeRet( sema: *Sema, block: *Scope.Block, - operand: Air.Inst.Ref, + uncasted_operand: Air.Inst.Ref, src: LazySrcLoc, need_coercion: bool, ) CompileError!Zir.Inst.Index { - const casted_operand = if (!need_coercion) operand else op: { - const func = sema.func.?; - const fn_ty = func.owner_decl.ty; - // TODO: In the case of a comptime/inline function call of a generic function, - // this needs to be the resolved return type based on the function parameter type - // expressions being evaluated with comptime arguments passed in. Otherwise, this - // ends up being .generic_poison and failing the comptime/inline function call analysis. - const fn_ret_ty = fn_ty.fnReturnType(); - break :op try sema.coerce(block, fn_ret_ty, operand, src); - }; + const operand = if (!need_coercion) + uncasted_operand + else + try sema.coerce(block, sema.fn_ret_ty, uncasted_operand, src); + if (block.inlining) |inlining| { // We are inlining a function call; rewrite the `ret` as a `break`. - try inlining.merges.results.append(sema.gpa, casted_operand); - _ = try block.addBr(inlining.merges.block_inst, casted_operand); + try inlining.merges.results.append(sema.gpa, operand); + _ = try block.addBr(inlining.merges.block_inst, operand); return always_noreturn; } - _ = try block.addUnOp(.ret, casted_operand); + _ = try block.addUnOp(.ret, operand); return always_noreturn; } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 91b48ffcfb..4b7454d878 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1093,21 +1093,32 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (!inst_ty.isInt()) - if (inst_ty.tag() != .bool) - return self.todo("implement 'airCmp' for type {}", .{inst_ty}); - - const is_signed = inst_ty.isSignedInt(); - const operation = switch (op) { - .eq => .EQ, - .neq => .NE, - .lt => @as(llvm.IntPredicate, if (is_signed) .SLT else .ULT), - .lte => @as(llvm.IntPredicate, if (is_signed) .SLE else .ULE), - .gt => @as(llvm.IntPredicate, if (is_signed) .SGT else .UGT), - .gte => @as(llvm.IntPredicate, if (is_signed) .SGE else .UGE), - }; - - return self.builder.buildICmp(operation, lhs, rhs, ""); + switch (self.air.typeOf(bin_op.lhs).zigTypeTag()) { + .Int, .Bool, .Pointer => { + const is_signed = inst_ty.isSignedInt(); + const operation = switch (op) { + .eq => .EQ, + .neq => .NE, + .lt => @as(llvm.IntPredicate, if (is_signed) .SLT else .ULT), + .lte => @as(llvm.IntPredicate, if (is_signed) .SLE else .ULE), + .gt => @as(llvm.IntPredicate, if (is_signed) .SGT else .UGT), + .gte => @as(llvm.IntPredicate, if (is_signed) .SGE else .UGE), + }; + return self.builder.buildICmp(operation, lhs, rhs, ""); + }, + .Float => { + const operation: llvm.RealPredicate = switch (op) { + .eq => .OEQ, + .neq => .UNE, + .lt => .OLT, + .lte => .OLE, + .gt => .OGT, + .gte => .OGE, + }; + return self.builder.buildFCmp(operation, lhs, rhs, ""); + }, + else => unreachable, + } } fn airBlock(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 675c5539fb..096cd4a5b4 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -409,6 +409,9 @@ pub const Builder = opaque { pub const buildICmp = LLVMBuildICmp; extern fn LLVMBuildICmp(*const Builder, Op: IntPredicate, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildFCmp = LLVMBuildFCmp; + extern fn LLVMBuildFCmp(*const Builder, Op: RealPredicate, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildBr = LLVMBuildBr; extern fn LLVMBuildBr(*const Builder, Dest: *const BasicBlock) *const Value; @@ -451,7 +454,7 @@ pub const Builder = opaque { ) *const Value; }; -pub const IntPredicate = enum(c_int) { +pub const IntPredicate = enum(c_uint) { EQ = 32, NE = 33, UGT = 34, @@ -464,6 +467,23 @@ pub const IntPredicate = enum(c_int) { SLE = 41, }; +pub const RealPredicate = enum(c_uint) { + OEQ = 1, + OGT = 2, + OGE = 3, + OLT = 4, + OLE = 5, + ONE = 6, + ORD = 7, + UNO = 8, + UEQ = 9, + UGT = 10, + UGE = 11, + ULT = 12, + ULE = 13, + UNE = 14, +}; + pub const BasicBlock = opaque { pub const deleteBasicBlock = LLVMDeleteBasicBlock; extern fn LLVMDeleteBasicBlock(BB: *const BasicBlock) void; diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index a3c5668b39..bb5504b479 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -64,9 +64,41 @@ fn sameButWithFloats(a: f64, b: f64) f64 { test "fn with comptime args" { try expect(gimmeTheBigOne(1234, 5678) == 5678); try expect(shouldCallSameInstance(34, 12) == 34); + try expect(sameButWithFloats(0.43, 0.49) == 0.49); +} + +test "anytype params" { + try expect(max_i32(12, 34) == 34); + try expect(max_f64(1.2, 3.4) == 3.4); if (!builtin.zig_is_stage2) { - // TODO: stage2 llvm backend needs to use fcmp instead of icmp - // probably AIR should just have different instructions for floats. - try expect(sameButWithFloats(0.43, 0.49) == 0.49); + // TODO: stage2 is incorrectly hitting the following problem: + // error: unable to resolve comptime value + // return max_anytype(a, b); + // ^ + comptime { + try expect(max_i32(12, 34) == 34); + try expect(max_f64(1.2, 3.4) == 3.4); + } + } +} + +fn max_anytype(a: anytype, b: anytype) @TypeOf(a, b) { + if (!builtin.zig_is_stage2) { + // TODO: stage2 is incorrectly emitting AIR that allocates a result + // value, stores to it, but then returns void instead of the result. + return if (a > b) a else b; } + if (a > b) { + return a; + } else { + return b; + } +} + +fn max_i32(a: i32, b: i32) i32 { + return max_anytype(a, b); +} + +fn max_f64(a: f64, b: f64) f64 { + return max_anytype(a, b); } diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig index 7ee263b1b9..4b768a5b4f 100644 --- a/test/behavior/generics_stage1.zig +++ b/test/behavior/generics_stage1.zig @@ -3,28 +3,6 @@ const testing = std.testing; const expect = testing.expect; const expectEqual = testing.expectEqual; -test "anytype params" { - try expect(max_i32(12, 34) == 34); - try expect(max_f64(1.2, 3.4) == 3.4); -} - -test { - comptime try expect(max_i32(12, 34) == 34); - comptime try expect(max_f64(1.2, 3.4) == 3.4); -} - -fn max_anytype(a: anytype, b: anytype) @TypeOf(a + b) { - return if (a > b) a else b; -} - -fn max_i32(a: i32, b: i32) i32 { - return max_anytype(a, b); -} - -fn max_f64(a: f64, b: f64) f64 { - return max_anytype(a, b); -} - pub fn List(comptime T: type) type { return SmallList(T, 8); } -- cgit v1.2.3 From e974d4c4295c4fbdbc239caa2cf2d653f65662f1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Aug 2021 17:26:37 -0700 Subject: stage2: get rid of "unable to monomorphize function" error This commit solves the problem in a much simpler way: putting runtime-known values in place of non-comptime arguments when instantiating a generic function. --- src/Module.zig | 10 ++++++++++ src/Sema.zig | 55 +++++++++++++++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 22 deletions(-) (limited to 'src/Module.zig') diff --git a/src/Module.zig b/src/Module.zig index 3652a27927..42b36b5a04 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1376,6 +1376,16 @@ pub const Scope = struct { }); } + pub fn addArg(block: *Block, ty: Type, name: u32) error{OutOfMemory}!Air.Inst.Ref { + return block.addInst(.{ + .tag = .arg, + .data = .{ .ty_str = .{ + .ty = try block.sema.addType(ty), + .str = name, + } }, + }); + } + pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref { return Air.indexToRef(try block.addInstAsIndex(inst)); } diff --git a/src/Sema.zig b/src/Sema.zig index 8bf4111566..87df3c375f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2793,16 +2793,24 @@ fn analyzeCall( } else if (is_comptime) { return sema.failWithNeededComptime(block, arg_src); } else if (is_anytype) { - const child_arg = try child_sema.addConstant( - sema.typeOf(arg), - Value.initTag(.generic_poison), - ); + // We insert into the map an instruction which is runtime-known + // but has the type of the comptime argument. + const child_arg = try child_block.addArg(sema.typeOf(arg), 0); child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); } arg_i += 1; } - const new_func_inst = try child_sema.resolveBody(&child_block, fn_info.param_body); - const new_func_val = try child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst); + const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body) catch |err| { + // TODO look up the compile error that happened here and attach a note to it + // pointing here, at the generic instantiation callsite. + if (sema.owner_func) |owner_func| { + owner_func.state = .dependency_failure; + } else { + sema.owner_decl.analysis = .dependency_failure; + } + return err; + }; + const new_func_val = child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst) catch unreachable; const new_func = new_func_val.castTag(.function).?.data; assert(new_func == new_module_func); @@ -2813,12 +2821,22 @@ fn analyzeCall( else => continue, } const arg = child_sema.inst_map.get(inst).?; - const arg_val = (child_sema.resolveMaybeUndefValAllowVariables(&child_block, .unneeded, arg) catch unreachable).?; - - child_sema.comptime_args[arg_i] = .{ - .ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator), - .val = try arg_val.copy(&new_decl_arena.allocator), - }; + const copied_arg_ty = try child_sema.typeOf(arg).copy(&new_decl_arena.allocator); + if (child_sema.resolveMaybeUndefValAllowVariables( + &child_block, + .unneeded, + arg, + ) catch unreachable) |arg_val| { + child_sema.comptime_args[arg_i] = .{ + .ty = copied_arg_ty, + .val = try arg_val.copy(&new_decl_arena.allocator), + }; + } else { + child_sema.comptime_args[arg_i] = .{ + .ty = copied_arg_ty, + .val = Value.initTag(.generic_poison), + }; + } arg_i += 1; } @@ -2828,17 +2846,10 @@ fn analyzeCall( new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); new_decl.analysis = .complete; - if (new_decl.ty.fnInfo().is_generic) { - // TODO improve this error message. This can happen because of the parameter - // type expression or return type expression depending on runtime-provided values. - // The error message should be emitted in zirParam or funcCommon when it - // is determined that we are trying to instantiate a generic function. - return mod.fail(&block.base, call_src, "unable to monomorphize function", .{}); - } - log.debug("generic function '{s}' instantiated with type {}", .{ new_decl.name, new_decl.ty, }); + assert(!new_decl.ty.fnInfo().is_generic); // The generic function Decl is guaranteed to be the first dependency // of each of its instantiations. @@ -8371,8 +8382,8 @@ fn analyzeDeclVal( fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { try sema.mod.declareDeclDependency(sema.owner_decl, decl); sema.mod.ensureDeclAnalyzed(decl) catch |err| { - if (sema.func) |func| { - func.state = .dependency_failure; + if (sema.owner_func) |owner_func| { + owner_func.state = .dependency_failure; } else { sema.owner_decl.analysis = .dependency_failure; } -- cgit v1.2.3