diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2021-08-26 14:35:34 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2021-08-26 14:35:34 +0200 |
| commit | 29df0ca39db8c336d6bdc7090f6d5f51b7463602 (patch) | |
| tree | 8f26542420e73f0588c6d4f167711a318fb23dfd /src | |
| parent | 570c75cb7440935990338ee733cce4a0b966c57b (diff) | |
| parent | 9c95f38a7c1defc7f63a4815bfc2d76a5f9f83f6 (diff) | |
| download | zig-29df0ca39db8c336d6bdc7090f6d5f51b7463602.tar.gz zig-29df0ca39db8c336d6bdc7090f6d5f51b7463602.zip | |
Merge remote-tracking branch 'origin/master' into zld-incr
Diffstat (limited to 'src')
| -rw-r--r-- | src/Air.zig | 33 | ||||
| -rw-r--r-- | src/AstGen.zig | 64 | ||||
| -rw-r--r-- | src/Compilation.zig | 1 | ||||
| -rw-r--r-- | src/Liveness.zig | 10 | ||||
| -rw-r--r-- | src/Module.zig | 398 | ||||
| -rw-r--r-- | src/Sema.zig | 943 | ||||
| -rw-r--r-- | src/TypedValue.zig | 15 | ||||
| -rw-r--r-- | src/Zir.zig | 13 | ||||
| -rw-r--r-- | src/codegen.zig | 185 | ||||
| -rw-r--r-- | src/codegen/arm.zig | 87 | ||||
| -rw-r--r-- | src/codegen/c.zig | 55 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 120 | ||||
| -rw-r--r-- | src/codegen/llvm/bindings.zig | 17 | ||||
| -rw-r--r-- | src/codegen/wasm.zig | 26 | ||||
| -rw-r--r-- | src/link/Wasm.zig | 8 | ||||
| -rw-r--r-- | src/main.zig | 1 | ||||
| -rw-r--r-- | src/mingw.zig | 36 | ||||
| -rw-r--r-- | src/print_air.zig | 22 | ||||
| -rw-r--r-- | src/stage1/analyze.cpp | 43 | ||||
| -rw-r--r-- | src/stage1/astgen.cpp | 49 | ||||
| -rw-r--r-- | src/stage1/codegen.cpp | 13 | ||||
| -rw-r--r-- | src/stage1/ir.cpp | 39 | ||||
| -rw-r--r-- | src/stage1/target.cpp | 2 | ||||
| -rw-r--r-- | src/target.zig | 2 | ||||
| -rw-r--r-- | src/translate_c.zig | 79 | ||||
| -rw-r--r-- | src/translate_c/ast.zig | 46 | ||||
| -rw-r--r-- | src/type.zig | 62 | ||||
| -rw-r--r-- | src/value.zig | 137 |
28 files changed, 1771 insertions, 735 deletions
diff --git a/src/Air.zig b/src/Air.zig index 391683afd5..6e4125be44 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -94,6 +94,12 @@ pub const Inst = struct { /// Result type is the same as both operands. /// Uses the `bin_op` field. bit_or, + /// Shift right. `>>` + /// Uses the `bin_op` field. + shr, + /// Shift left. `<<` + /// Uses the `bin_op` field. + shl, /// Bitwise XOR. `^` /// Uses the `bin_op` field. xor, @@ -258,6 +264,13 @@ pub const Inst = struct { /// Given a pointer to a struct and a field index, returns a pointer to the field. /// Uses the `ty_pl` field, payload is `StructField`. struct_field_ptr, + /// Given a pointer to a struct, returns a pointer to the field. + /// The field index is the number at the end of the name. + /// Uses `ty_op` field. + struct_field_ptr_index_0, + struct_field_ptr_index_1, + struct_field_ptr_index_2, + struct_field_ptr_index_3, /// Given a byval struct and a field index, returns the field byval. /// Uses the `ty_pl` field, payload is `StructField`. struct_field_val, @@ -280,6 +293,10 @@ pub const Inst = struct { /// Result type is the element type of the pointer operand. /// Uses the `bin_op` field. ptr_elem_val, + /// Given a pointer value, and element index, return the element pointer at that index. + /// Result type is pointer to the element type of the pointer operand. + /// Uses the `ty_pl` field with payload `Bin`. + ptr_elem_ptr, /// Given a pointer to a pointer, and element index, return the element value of the inner /// pointer at that index. /// Result type is the element type of the inner pointer operand. @@ -404,6 +421,11 @@ pub const StructField = struct { field_index: u32, }; +pub const Bin = struct { + lhs: Inst.Ref, + rhs: Inst.Ref, +}; + /// Trailing: /// 0. `Inst.Ref` for every outputs_len /// 1. `Inst.Ref` for every inputs_len @@ -445,6 +467,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .xor, .ptr_add, .ptr_sub, + .shr, + .shl, => return air.typeOf(datas[inst].bin_op.lhs), .cmp_lt, @@ -474,6 +498,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .constant, .struct_field_ptr, .struct_field_val, + .ptr_elem_ptr, => return air.getRefType(datas[inst].ty_pl.ty), .not, @@ -492,6 +517,10 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .wrap_errunion_payload, .wrap_errunion_err, .slice_ptr, + .struct_field_ptr_index_0, + .struct_field_ptr_index_1, + .struct_field_ptr_index_2, + .struct_field_ptr_index_3, => return air.getRefType(datas[inst].ty_op.ty), .loop, @@ -519,8 +548,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { }, .slice_elem_val, .ptr_elem_val => { - const slice_ty = air.typeOf(datas[inst].bin_op.lhs); - return slice_ty.elemType(); + const ptr_ty = air.typeOf(datas[inst].bin_op.lhs); + return ptr_ty.elemType(); }, .ptr_slice_elem_val, .ptr_ptr_elem_val => { const outer_ptr_ty = air.typeOf(datas[inst].bin_op.lhs); diff --git a/src/AstGen.zig b/src/AstGen.zig index e3f33ec332..2b2bbd4f22 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7102,38 +7102,38 @@ fn builtinCall( .bit_size_of => return simpleUnOpType(gz, scope, rl, node, params[0], .bit_size_of), .align_of => return simpleUnOpType(gz, scope, rl, node, params[0], .align_of), - .ptr_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ptr_to_int), - .error_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .error_to_int), - .int_to_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u16_type }, params[0], .int_to_error), - .compile_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .compile_error), - .set_eval_branch_quota => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u32_type }, params[0], .set_eval_branch_quota), - .enum_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .enum_to_int), - .bool_to_int => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .bool_to_int), - .embed_file => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .embed_file), - .error_name => return simpleUnOp(gz, scope, rl, node, .{ .ty = .anyerror_type }, params[0], .error_name), - .panic => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .panic), - .set_align_stack => return simpleUnOp(gz, scope, rl, node, align_rl, params[0], .set_align_stack), - .set_cold => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .set_cold), - .set_float_mode => return simpleUnOp(gz, scope, rl, node, .{ .ty = .float_mode_type }, params[0], .set_float_mode), - .set_runtime_safety => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .set_runtime_safety), - .sqrt => return simpleUnOp(gz, scope, rl, node, .none, params[0], .sqrt), - .sin => return simpleUnOp(gz, scope, rl, node, .none, params[0], .sin), - .cos => return simpleUnOp(gz, scope, rl, node, .none, params[0], .cos), - .exp => return simpleUnOp(gz, scope, rl, node, .none, params[0], .exp), - .exp2 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .exp2), - .log => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log), - .log2 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log2), - .log10 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log10), - .fabs => return simpleUnOp(gz, scope, rl, node, .none, params[0], .fabs), - .floor => return simpleUnOp(gz, scope, rl, node, .none, params[0], .floor), - .ceil => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ceil), - .trunc => return simpleUnOp(gz, scope, rl, node, .none, params[0], .trunc), - .round => return simpleUnOp(gz, scope, rl, node, .none, params[0], .round), - .tag_name => return simpleUnOp(gz, scope, rl, node, .none, params[0], .tag_name), - .Type => return simpleUnOp(gz, scope, rl, node, .none, params[0], .reify), - .type_name => return simpleUnOp(gz, scope, rl, node, .none, params[0], .type_name), - .Frame => return simpleUnOp(gz, scope, rl, node, .none, params[0], .frame_type), - .frame_size => return simpleUnOp(gz, scope, rl, node, .none, params[0], .frame_size), + .ptr_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ptr_to_int), + .error_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .error_to_int), + .int_to_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u16_type }, params[0], .int_to_error), + .compile_error => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .compile_error), + .set_eval_branch_quota => return simpleUnOp(gz, scope, rl, node, .{ .ty = .u32_type }, params[0], .set_eval_branch_quota), + .enum_to_int => return simpleUnOp(gz, scope, rl, node, .none, params[0], .enum_to_int), + .bool_to_int => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .bool_to_int), + .embed_file => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .embed_file), + .error_name => return simpleUnOp(gz, scope, rl, node, .{ .ty = .anyerror_type }, params[0], .error_name), + .panic => return simpleUnOp(gz, scope, rl, node, .{ .ty = .const_slice_u8_type }, params[0], .panic), + .set_align_stack => return simpleUnOp(gz, scope, rl, node, align_rl, params[0], .set_align_stack), + .set_cold => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .set_cold), + .set_float_mode => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .float_mode_type }, params[0], .set_float_mode), + .set_runtime_safety => return simpleUnOp(gz, scope, rl, node, bool_rl, params[0], .set_runtime_safety), + .sqrt => return simpleUnOp(gz, scope, rl, node, .none, params[0], .sqrt), + .sin => return simpleUnOp(gz, scope, rl, node, .none, params[0], .sin), + .cos => return simpleUnOp(gz, scope, rl, node, .none, params[0], .cos), + .exp => return simpleUnOp(gz, scope, rl, node, .none, params[0], .exp), + .exp2 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .exp2), + .log => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log), + .log2 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log2), + .log10 => return simpleUnOp(gz, scope, rl, node, .none, params[0], .log10), + .fabs => return simpleUnOp(gz, scope, rl, node, .none, params[0], .fabs), + .floor => return simpleUnOp(gz, scope, rl, node, .none, params[0], .floor), + .ceil => return simpleUnOp(gz, scope, rl, node, .none, params[0], .ceil), + .trunc => return simpleUnOp(gz, scope, rl, node, .none, params[0], .trunc), + .round => return simpleUnOp(gz, scope, rl, node, .none, params[0], .round), + .tag_name => return simpleUnOp(gz, scope, rl, node, .none, params[0], .tag_name), + .Type => return simpleUnOp(gz, scope, rl, node, .{ .coerced_ty = .type_info_type }, params[0], .reify), + .type_name => return simpleUnOp(gz, scope, rl, node, .none, params[0], .type_name), + .Frame => return simpleUnOp(gz, scope, rl, node, .none, params[0], .frame_type), + .frame_size => return simpleUnOp(gz, scope, rl, node, .none, params[0], .frame_size), .float_to_int => return typeCast(gz, scope, rl, node, params[0], params[1], .float_to_int), .int_to_float => return typeCast(gz, scope, rl, node, params[0], params[1], .int_to_float), diff --git a/src/Compilation.zig b/src/Compilation.zig index 7987ed95df..7dc726aeb2 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2557,6 +2557,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { var argv = std.ArrayList([]const u8).init(comp.gpa); defer argv.deinit(); + try argv.append(""); // argv[0] is program name, actual args start at [1] try comp.addTranslateCCArgs(arena, &argv, .c, out_dep_path); try argv.append(out_h_path); diff --git a/src/Liveness.zig b/src/Liveness.zig index 48603fc7c9..6a47bfe597 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -249,6 +249,8 @@ fn analyzeInst( .ptr_slice_elem_val, .ptr_elem_val, .ptr_ptr_elem_val, + .shl, + .shr, => { const o = inst_datas[inst].bin_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); @@ -280,6 +282,10 @@ fn analyzeInst( .wrap_errunion_err, .slice_ptr, .slice_len, + .struct_field_ptr_index_0, + .struct_field_ptr_index_1, + .struct_field_ptr_index_2, + .struct_field_ptr_index_3, => { const o = inst_datas[inst].ty_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none }); @@ -328,6 +334,10 @@ fn analyzeInst( const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_operand, .none, .none }); }, + .ptr_elem_ptr => { + const extra = a.air.extraData(Air.Bin, inst_datas[inst].ty_pl.payload).data; + return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, .none }); + }, .br => { const br = inst_datas[inst].br; return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none }); diff --git a/src/Module.zig b/src/Module.zig index 319363e9b8..4ed39c9954 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -66,6 +66,10 @@ import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{}, /// to the same function. monomorphed_funcs: MonomorphedFuncsSet = .{}, +/// The set of all comptime function calls that have been cached so that future calls +/// with the same parameters will get the same return value. +memoized_calls: MemoizedCallSet = .{}, + /// 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. @@ -157,6 +161,60 @@ const MonomorphedFuncsContext = struct { } }; +pub const MemoizedCallSet = std.HashMapUnmanaged( + MemoizedCall.Key, + MemoizedCall.Result, + MemoizedCall, + std.hash_map.default_max_load_percentage, +); + +pub const MemoizedCall = struct { + pub const Key = struct { + func: *Fn, + args: []TypedValue, + }; + + pub const Result = struct { + val: Value, + arena: std.heap.ArenaAllocator.State, + }; + + pub fn eql(ctx: @This(), a: Key, b: Key) bool { + _ = ctx; + + if (a.func != b.func) return false; + + assert(a.args.len == b.args.len); + for (a.args) |a_arg, arg_i| { + const b_arg = b.args[arg_i]; + if (!a_arg.eql(b_arg)) { + return false; + } + } + + return true; + } + + /// Must match `Sema.GenericCallAdapter.hash`. + pub fn hash(ctx: @This(), key: Key) 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. + std.hash.autoHash(&hasher, @ptrToInt(key.func)); + + // This logic must be kept in sync with the logic in `analyzeCall` that + // computes the hash. + for (key.args) |arg| { + arg.hash(&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. @@ -554,8 +612,8 @@ pub const Decl = struct { assert(struct_obj.owner_decl == decl); return &struct_obj.namespace; }, - .enum_full => { - const enum_obj = ty.castTag(.enum_full).?.data; + .enum_full, .enum_nonexhaustive => { + const enum_obj = ty.cast(Type.Payload.EnumFull).?.data; assert(enum_obj.owner_decl == decl); return &enum_obj.namespace; }, @@ -660,6 +718,7 @@ pub const Struct = struct { /// is necessary to determine whether it has bits at runtime. known_has_bits: bool, + /// The `Type` and `Value` memory is owned by the arena of the Struct's owner_decl. pub const Field = struct { /// Uses `noreturn` to indicate `anytype`. /// undefined until `status` is `have_field_types` or `have_layout`. @@ -2254,15 +2313,26 @@ pub fn deinit(mod: *Module) void { } mod.export_owners.deinit(gpa); - var it = mod.global_error_set.keyIterator(); - while (it.next()) |key| { - gpa.free(key.*); + { + var it = mod.global_error_set.keyIterator(); + while (it.next()) |key| { + gpa.free(key.*); + } + mod.global_error_set.deinit(gpa); } - mod.global_error_set.deinit(gpa); mod.error_name_list.deinit(gpa); mod.test_functions.deinit(gpa); mod.monomorphed_funcs.deinit(gpa); + + { + var it = mod.memoized_calls.iterator(); + while (it.next()) |entry| { + gpa.free(entry.key_ptr.args); + entry.value_ptr.arena.promote(gpa).deinit(); + } + mod.memoized_calls.deinit(gpa); + } } fn freeExportList(gpa: *Allocator, export_list: []*Export) void { @@ -3091,6 +3161,9 @@ 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; }; + // Note this resolves the type of the Decl, not the value; if this Decl + // is a struct, for example, this resolves `type` (which needs no resolution), + // not the struct itself. try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty); // We need the memory for the Type to go into the arena for the Decl @@ -3193,6 +3266,15 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { if (type_changed and mod.emit_h != null) { try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl }); } + } else if (decl_tv.ty.zigTypeTag() == .Type) { + // In case this Decl is a struct or union, we need to resolve the fields + // while we still have the `Sema` in scope, so that the field type expressions + // can use the resolved AIR instructions that they possibly reference. + // We do this after the decl is populated and set to `complete` so that a `Decl` + // may reference itself. + var buffer: Value.ToTypeBuffer = undefined; + const ty = decl.val.toType(&buffer); + try sema.resolveDeclFields(&block_scope, src, ty); } if (decl.is_exported) { @@ -4024,7 +4106,6 @@ pub fn createAnonymousDeclFromDeclNamed( new_decl.ty = typed_value.ty; new_decl.val = typed_value.val; new_decl.has_tv = true; - new_decl.owns_tv = true; new_decl.analysis = .complete; new_decl.generation = mod.generation; @@ -4450,309 +4531,6 @@ pub const PeerTypeCandidateSrc = union(enum) { } }; -pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void { - const tracy = trace(@src()); - defer tracy.end(); - - const gpa = mod.gpa; - const zir = struct_obj.owner_decl.namespace.file_scope.zir; - const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended; - assert(extended.opcode == .struct_decl); - const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small); - var extra_index: usize = extended.operand; - - const src: LazySrcLoc = .{ .node_offset = struct_obj.node_offset }; - extra_index += @boolToInt(small.has_src_node); - - const body_len = if (small.has_body_len) blk: { - const body_len = zir.extra[extra_index]; - extra_index += 1; - break :blk body_len; - } else 0; - - const fields_len = if (small.has_fields_len) blk: { - const fields_len = zir.extra[extra_index]; - extra_index += 1; - break :blk fields_len; - } else 0; - - const decls_len = if (small.has_decls_len) decls_len: { - const decls_len = zir.extra[extra_index]; - extra_index += 1; - break :decls_len decls_len; - } else 0; - - // Skip over decls. - var decls_it = zir.declIteratorInner(extra_index, decls_len); - while (decls_it.next()) |_| {} - extra_index = decls_it.extra_index; - - const body = zir.extra[extra_index..][0..body_len]; - if (fields_len == 0) { - assert(body.len == 0); - return; - } - extra_index += body.len; - - var decl_arena = struct_obj.owner_decl.value_arena.?.promote(gpa); - defer struct_obj.owner_decl.value_arena.?.* = decl_arena.state; - - try struct_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len); - - // We create a block for the field type instructions because they - // may need to reference Decls from inside the struct namespace. - // Within the field type, default value, and alignment expressions, the "owner decl" - // should be the struct itself. Thus we need a new Sema. - var sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = &decl_arena.allocator, - .code = zir, - .owner_decl = struct_obj.owner_decl, - .namespace = &struct_obj.namespace, - .owner_func = null, - .func = null, - .fn_ret_ty = Type.initTag(.void), - }; - defer sema.deinit(); - - var block: Scope.Block = .{ - .parent = null, - .sema = &sema, - .src_decl = struct_obj.owner_decl, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - }; - defer assert(block.instructions.items.len == 0); // should all be comptime instructions - - if (body.len != 0) { - _ = try sema.analyzeBody(&block, body); - } - - const bits_per_field = 4; - const fields_per_u32 = 32 / bits_per_field; - const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; - var bit_bag_index: usize = extra_index; - extra_index += bit_bags_count; - var cur_bit_bag: u32 = undefined; - var field_i: u32 = 0; - while (field_i < fields_len) : (field_i += 1) { - if (field_i % fields_per_u32 == 0) { - cur_bit_bag = zir.extra[bit_bag_index]; - bit_bag_index += 1; - } - const has_align = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const has_default = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const is_comptime = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const unused = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - - _ = unused; - - const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]); - extra_index += 1; - const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); - extra_index += 1; - - // This string needs to outlive the ZIR code. - const field_name = try decl_arena.allocator.dupe(u8, field_name_zir); - if (field_type_ref == .none) { - return mod.fail(&block.base, src, "TODO: implement anytype struct field", .{}); - } - const field_ty: Type = if (field_type_ref == .none) - Type.initTag(.noreturn) - else - // TODO: if we need to report an error here, use a source location - // that points to this type expression rather than the struct. - // But only resolve the source location if we need to emit a compile error. - try sema.resolveType(&block, src, field_type_ref); - - const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name); - assert(!gop.found_existing); - gop.value_ptr.* = .{ - .ty = field_ty, - .abi_align = Value.initTag(.abi_align_default), - .default_val = Value.initTag(.unreachable_value), - .is_comptime = is_comptime, - .offset = undefined, - }; - - if (has_align) { - const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); - extra_index += 1; - // TODO: if we need to report an error here, use a source location - // that points to this alignment expression rather than the struct. - // But only resolve the source location if we need to emit a compile error. - gop.value_ptr.abi_align = (try sema.resolveInstConst(&block, src, align_ref)).val; - } - if (has_default) { - const default_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); - extra_index += 1; - // TODO: if we need to report an error here, use a source location - // 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. - gop.value_ptr.default_val = (try sema.resolveInstConst(&block, src, default_ref)).val; - } - } -} - -pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void { - const tracy = trace(@src()); - defer tracy.end(); - - const gpa = mod.gpa; - const zir = union_obj.owner_decl.namespace.file_scope.zir; - const extended = zir.instructions.items(.data)[union_obj.zir_index].extended; - assert(extended.opcode == .union_decl); - const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small); - var extra_index: usize = extended.operand; - - const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset }; - extra_index += @boolToInt(small.has_src_node); - - if (small.has_tag_type) { - extra_index += 1; - } - - const body_len = if (small.has_body_len) blk: { - const body_len = zir.extra[extra_index]; - extra_index += 1; - break :blk body_len; - } else 0; - - const fields_len = if (small.has_fields_len) blk: { - const fields_len = zir.extra[extra_index]; - extra_index += 1; - break :blk fields_len; - } else 0; - - const decls_len = if (small.has_decls_len) decls_len: { - const decls_len = zir.extra[extra_index]; - extra_index += 1; - break :decls_len decls_len; - } else 0; - - // Skip over decls. - var decls_it = zir.declIteratorInner(extra_index, decls_len); - while (decls_it.next()) |_| {} - extra_index = decls_it.extra_index; - - const body = zir.extra[extra_index..][0..body_len]; - if (fields_len == 0) { - assert(body.len == 0); - return; - } - extra_index += body.len; - - var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa); - defer union_obj.owner_decl.value_arena.?.* = decl_arena.state; - - try union_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len); - - // We create a block for the field type instructions because they - // may need to reference Decls from inside the struct namespace. - // Within the field type, default value, and alignment expressions, the "owner decl" - // should be the struct itself. Thus we need a new Sema. - var sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = &decl_arena.allocator, - .code = zir, - .owner_decl = union_obj.owner_decl, - .namespace = &union_obj.namespace, - .owner_func = null, - .func = null, - .fn_ret_ty = Type.initTag(.void), - }; - defer sema.deinit(); - - var block: Scope.Block = .{ - .parent = null, - .sema = &sema, - .src_decl = union_obj.owner_decl, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - }; - defer assert(block.instructions.items.len == 0); // should all be comptime instructions - - if (body.len != 0) { - _ = try sema.analyzeBody(&block, body); - } - - const bits_per_field = 4; - const fields_per_u32 = 32 / bits_per_field; - const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; - var bit_bag_index: usize = extra_index; - extra_index += bit_bags_count; - var cur_bit_bag: u32 = undefined; - var field_i: u32 = 0; - while (field_i < fields_len) : (field_i += 1) { - if (field_i % fields_per_u32 == 0) { - cur_bit_bag = zir.extra[bit_bag_index]; - bit_bag_index += 1; - } - const has_type = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const has_align = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const has_tag = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const unused = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - _ = unused; - - const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]); - extra_index += 1; - - const field_type_ref: Zir.Inst.Ref = if (has_type) blk: { - const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); - extra_index += 1; - break :blk field_type_ref; - } else .none; - - const align_ref: Zir.Inst.Ref = if (has_align) blk: { - const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); - extra_index += 1; - break :blk align_ref; - } else .none; - - if (has_tag) { - extra_index += 1; - } - - // This string needs to outlive the ZIR code. - const field_name = try decl_arena.allocator.dupe(u8, field_name_zir); - const field_ty: Type = if (field_type_ref == .none) - Type.initTag(.void) - else - // TODO: if we need to report an error here, use a source location - // that points to this type expression rather than the union. - // But only resolve the source location if we need to emit a compile error. - try sema.resolveType(&block, src, field_type_ref); - - const gop = union_obj.fields.getOrPutAssumeCapacity(field_name); - assert(!gop.found_existing); - gop.value_ptr.* = .{ - .ty = field_ty, - .abi_align = Value.initTag(.abi_align_default), - }; - - if (align_ref != .none) { - // TODO: if we need to report an error here, use a source location - // that points to this alignment expression rather than the struct. - // But only resolve the source location if we need to emit a compile error. - gop.value_ptr.abi_align = (try sema.resolveInstConst(&block, src, align_ref)).val; - } - } - - // TODO resolve the union tag_type_ref -} - /// Called from `performAllTheWork`, after all AstGen workers have finished, /// and before the main semantic analysis loop begins. pub fn processOutdatedAndDeletedDecls(mod: *Module) !void { diff --git a/src/Sema.zig b/src/Sema.zig index 0813f749e2..a11bdec66d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -649,6 +649,24 @@ fn resolveValue( return sema.failWithNeededComptime(block, src); } +/// Value Tag `variable` will cause a compile error. +/// Value Tag `undef` may be returned. +fn resolveConstMaybeUndefVal( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + inst: Air.Inst.Ref, +) CompileError!Value { + if (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) |val| { + switch (val.tag()) { + .variable => return sema.failWithNeededComptime(block, src), + .generic_poison => return error.GenericPoison, + else => 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( @@ -866,6 +884,7 @@ fn zirStructDecl( .ty = Type.initTag(.type), .val = struct_val, }, type_name); + new_decl.owns_tv = true; errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); struct_obj.* = .{ .owner_decl = new_decl, @@ -986,6 +1005,7 @@ fn zirEnumDecl( .ty = Type.initTag(.type), .val = enum_val, }, type_name); + new_decl.owns_tv = true; errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); enum_obj.* = .{ @@ -1032,25 +1052,27 @@ fn zirEnumDecl( // We create a block for the field type instructions because they // may need to reference Decls from inside the enum namespace. // Within the field type, default value, and alignment expressions, the "owner decl" - // should be the enum itself. Thus we need a new Sema. - var enum_sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = &new_decl_arena.allocator, - .code = sema.code, - .inst_map = sema.inst_map, - .owner_decl = new_decl, - .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, - }; + // should be the enum itself. + + const prev_owner_decl = sema.owner_decl; + sema.owner_decl = new_decl; + defer sema.owner_decl = prev_owner_decl; + + const prev_namespace = sema.namespace; + sema.namespace = &enum_obj.namespace; + defer sema.namespace = prev_namespace; + + const prev_owner_func = sema.owner_func; + sema.owner_func = null; + defer sema.owner_func = prev_owner_func; + + const prev_func = sema.func; + sema.func = null; + defer sema.func = prev_func; var enum_block: Scope.Block = .{ .parent = null, - .sema = &enum_sema, + .sema = sema, .src_decl = new_decl, .instructions = .{}, .inlining = null, @@ -1059,11 +1081,8 @@ fn zirEnumDecl( defer assert(enum_block.instructions.items.len == 0); // should all be comptime instructions if (body.len != 0) { - _ = try enum_sema.analyzeBody(&enum_block, body); + _ = try sema.analyzeBody(&enum_block, body); } - - sema.branch_count = enum_sema.branch_count; - sema.branch_quota = enum_sema.branch_quota; } var bit_bag_index: usize = body_end; var cur_bit_bag: u32 = undefined; @@ -1153,6 +1172,7 @@ fn zirUnionDecl( .ty = Type.initTag(.type), .val = union_val, }, type_name); + new_decl.owns_tv = true; errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); union_obj.* = .{ .owner_decl = new_decl, @@ -1224,6 +1244,7 @@ fn zirErrorSetDecl( .ty = Type.initTag(.type), .val = error_set_val, }, type_name); + new_decl.owns_tv = true; errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); const names = try new_decl_arena.allocator.alloc([]const u8, fields.len); for (fields) |str_index, i| { @@ -1466,8 +1487,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde const ptr = sema.resolveInst(inst_data.operand); const ptr_inst = Air.refToIndex(ptr).?; assert(sema.air_instructions.items(.tag)[ptr_inst] == .constant); - const air_datas = sema.air_instructions.items(.data); - const value_index = air_datas[ptr_inst].ty_pl.payload; + const value_index = sema.air_instructions.items(.data)[ptr_inst].ty_pl.payload; const ptr_val = sema.air_values.items[value_index]; const var_is_mut = switch (sema.typeOf(ptr).tag()) { .inferred_alloc_const => false, @@ -1481,7 +1501,8 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde const final_elem_ty = try decl.ty.copy(sema.arena); const final_ptr_ty = try Module.simplePtrType(sema.arena, final_elem_ty, true, .One); - air_datas[ptr_inst].ty_pl.ty = try sema.addType(final_ptr_ty); + const final_ptr_ty_inst = try sema.addType(final_ptr_ty); + sema.air_instructions.items(.data)[ptr_inst].ty_pl.ty = final_ptr_ty_inst; if (var_is_mut) { sema.air_values.items[value_index] = try Value.Tag.decl_ref_mut.create(sema.arena, .{ @@ -2562,6 +2583,19 @@ fn analyzeCall( defer merges.results.deinit(gpa); defer merges.br_list.deinit(gpa); + // If it's a comptime function call, we need to memoize it as long as no external + // comptime memory is mutated. + var memoized_call_key: Module.MemoizedCall.Key = undefined; + var delete_memoized_call_key = false; + defer if (delete_memoized_call_key) gpa.free(memoized_call_key.args); + if (is_comptime_call) { + memoized_call_key = .{ + .func = module_fn, + .args = try gpa.alloc(TypedValue, func_ty_info.param_types.len), + }; + delete_memoized_call_key = true; + } + try sema.emitBackwardBranch(&child_block, call_src); // This will have return instructions analyzed as break instructions to @@ -2586,12 +2620,32 @@ fn analyzeCall( 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); + + if (is_comptime_call) { + const arg_val = try sema.resolveConstMaybeUndefVal(&child_block, arg_src, casted_arg); + memoized_call_key.args[arg_i] = .{ + .ty = param_ty, + .val = arg_val, + }; + } + arg_i += 1; continue; }, .param_anytype, .param_anytype_comptime => { // No coercion needed. - try sema.inst_map.putNoClobber(gpa, inst, uncasted_args[arg_i]); + const uncasted_arg = uncasted_args[arg_i]; + try sema.inst_map.putNoClobber(gpa, inst, uncasted_arg); + + if (is_comptime_call) { + const arg_src = call_src; // TODO: better source location + const arg_val = try sema.resolveConstMaybeUndefVal(&child_block, arg_src, uncasted_arg); + memoized_call_key.args[arg_i] = .{ + .ty = sema.typeOf(uncasted_arg), + .val = arg_val, + }; + } + arg_i += 1; continue; }, @@ -2623,8 +2677,61 @@ fn analyzeCall( 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); + // This `res2` is here instead of directly breaking from `res` due to a stage1 + // bug generating invalid LLVM IR. + const res2: Air.Inst.Ref = res2: { + if (is_comptime_call) { + if (mod.memoized_calls.get(memoized_call_key)) |result| { + const ty_inst = try sema.addType(fn_ret_ty); + try sema.air_values.append(gpa, result.val); + sema.air_instructions.set(block_inst, .{ + .tag = .constant, + .data = .{ .ty_pl = .{ + .ty = ty_inst, + .payload = @intCast(u32, sema.air_values.items.len - 1), + } }, + }); + break :res2 Air.indexToRef(block_inst); + } + } + + _ = try sema.analyzeBody(&child_block, fn_info.body); + const result = try sema.analyzeBlockBody(block, call_src, &child_block, merges); + + if (is_comptime_call) { + const result_val = try sema.resolveConstMaybeUndefVal(block, call_src, result); + + // TODO: check whether any external comptime memory was mutated by the + // comptime function call. If so, then do not memoize the call here. + { + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + errdefer arena_allocator.deinit(); + const arena = &arena_allocator.allocator; + + for (memoized_call_key.args) |*arg| { + arg.* = try arg.*.copy(arena); + } + + try mod.memoized_calls.put(gpa, memoized_call_key, .{ + .val = result_val, + .arena = arena_allocator.state, + }); + delete_memoized_call_key = false; + } + + // Much like in `Module.semaDecl`, if the result is a struct or union type, + // we need to resolve the field type expressions right here, right now, while + // the child `Sema` is still available, with the AIR instruction map intact, + // because the field type expressions may reference into it. + if (sema.typeOf(result).zigTypeTag() == .Type) { + const ty = try sema.analyzeAsType(&child_block, call_src, result); + try sema.resolveDeclFields(&child_block, call_src, ty); + } + } + + break :res2 result; + }; + break :res res2; } 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; @@ -3291,31 +3398,9 @@ fn zirEnumToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE } if (try sema.resolveMaybeUndefVal(block, operand_src, enum_tag)) |enum_tag_val| { - if (enum_tag_val.castTag(.enum_field_index)) |enum_field_payload| { - const field_index = enum_field_payload.data; - switch (enum_tag_ty.tag()) { - .enum_full => { - const enum_full = enum_tag_ty.castTag(.enum_full).?.data; - if (enum_full.values.count() != 0) { - const val = enum_full.values.keys()[field_index]; - return sema.addConstant(int_tag_ty, val); - } else { - // Field index and integer values are the same. - const val = try Value.Tag.int_u64.create(arena, field_index); - return sema.addConstant(int_tag_ty, val); - } - }, - .enum_simple => { - // Field index and integer values are the same. - const val = try Value.Tag.int_u64.create(arena, field_index); - return sema.addConstant(int_tag_ty, val); - }, - else => unreachable, - } - } else { - // Assume it is already an integer and return it directly. - return sema.addConstant(int_tag_ty, enum_tag_val); - } + var buffer: Value.Payload.U64 = undefined; + const val = enum_tag_val.enumToInt(enum_tag_ty, &buffer); + return sema.addConstant(int_tag_ty, try val.copy(sema.arena)); } try sema.requireRuntimeBlock(block, src); @@ -3400,7 +3485,10 @@ fn zirOptionalPayloadPtr( 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); + return sema.addConstant( + child_pointer, + try Value.Tag.opt_payload_ptr.create(sema.arena, pointer_val), + ); } } @@ -3437,7 +3525,8 @@ fn zirOptionalPayload( if (val.isNull()) { return sema.mod.fail(&block.base, src, "unable to unwrap null", .{}); } - return sema.addConstant(child_type, val); + const sub_val = val.castTag(.opt_payload).?.data; + return sema.addConstant(child_type, sub_val); } try sema.requireRuntimeBlock(block, src); @@ -5294,17 +5383,56 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A const tracy = trace(@src()); defer tracy.end(); - _ = block; - _ = inst; - return sema.mod.fail(&block.base, sema.src, "TODO implement zirShl", .{}); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = sema.resolveInst(extra.lhs); + const rhs = sema.resolveInst(extra.rhs); + + 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(sema.typeOf(lhs)); + } + return sema.mod.fail(&block.base, src, "TODO implement comptime shl", .{}); + } + } + + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.shl, lhs, rhs); } fn zirShr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); - _ = inst; - return sema.mod.fail(&block.base, sema.src, "TODO implement zirShr", .{}); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = sema.resolveInst(extra.lhs); + const rhs = sema.resolveInst(extra.rhs); + + if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { + if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { + const lhs_ty = sema.typeOf(lhs); + if (lhs_val.isUndef() or rhs_val.isUndef()) { + return sema.addConstUndef(lhs_ty); + } + // If rhs is 0, return lhs without doing any calculations. + if (rhs_val.compareWithZero(.eq)) { + return sema.addConstant(lhs_ty, lhs_val); + } + const val = try lhs_val.shr(rhs_val, sema.arena); + return sema.addConstant(lhs_ty, val); + } + } + + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.shr, lhs, rhs); } fn zirBitwise( @@ -5975,6 +6103,28 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr }), ); }, + .Int => { + const info = ty.intInfo(target); + const field_values = try sema.arena.alloc(Value, 2); + // signedness: Signedness, + field_values[0] = try Value.Tag.enum_field_index.create( + sema.arena, + @enumToInt(info.signedness), + ); + // bits: comptime_int, + field_values[1] = try Value.Tag.int_u64.create(sema.arena, info.bits); + + return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create( + sema.arena, + @enumToInt(@typeInfo(std.builtin.TypeInfo).Union.tag_type.?.Int), + ), + .val = try Value.Tag.@"struct".create(sema.arena, field_values.ptr), + }), + ); + }, else => |t| return sema.mod.fail(&block.base, src, "TODO: implement zirTypeInfo for {s}", .{ @tagName(t), }), @@ -6001,13 +6151,37 @@ fn zirTypeofElem(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile fn zirTypeofLog2IntType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirTypeofLog2IntType", .{}); + const operand = sema.resolveInst(inst_data.operand); + const operand_ty = sema.typeOf(operand); + return sema.log2IntType(block, operand_ty, src); } fn zirLog2IntType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirLog2IntType", .{}); + const operand = try sema.resolveType(block, src, inst_data.operand); + return sema.log2IntType(block, operand, src); +} + +fn log2IntType(sema: *Sema, block: *Scope.Block, operand: Type, src: LazySrcLoc) CompileError!Air.Inst.Ref { + switch (operand.zigTypeTag()) { + .ComptimeInt => return Air.Inst.Ref.comptime_int_type, + .Int => { + var count: u16 = 0; + var s = operand.bitSize(sema.mod.getTarget()) - 1; + while (s != 0) : (s >>= 1) { + count += 1; + } + const res = try Module.makeIntType(sema.arena, .unsigned, count); + return sema.addType(res); + }, + else => return sema.mod.fail( + &block.base, + src, + "bit shifting operation expected integer type, found '{}'", + .{operand}, + ), + } } fn zirTypeofPeer( @@ -6464,99 +6638,134 @@ fn zirStructInit(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref: const first_field_type_data = zir_datas[first_item.field_type].pl_node; const first_field_type_extra = sema.code.extraData(Zir.Inst.FieldType, first_field_type_data.payload_index).data; const unresolved_struct_type = try sema.resolveType(block, src, first_field_type_extra.container_type); - const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type); - const struct_obj = struct_ty.castTag(.@"struct").?.data; - - // Maps field index to field_type index of where it was already initialized. - // For making sure all fields are accounted for and no fields are duplicated. - const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.count()); - defer gpa.free(found_fields); - mem.set(Zir.Inst.Index, found_fields, 0); - - // The init values to use for the struct instance. - const field_inits = try gpa.alloc(Air.Inst.Ref, struct_obj.fields.count()); - defer gpa.free(field_inits); + const resolved_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type); + + if (resolved_ty.castTag(.@"struct")) |struct_payload| { + const struct_obj = struct_payload.data; + + // Maps field index to field_type index of where it was already initialized. + // For making sure all fields are accounted for and no fields are duplicated. + const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.count()); + defer gpa.free(found_fields); + mem.set(Zir.Inst.Index, found_fields, 0); + + // The init values to use for the struct instance. + const field_inits = try gpa.alloc(Air.Inst.Ref, struct_obj.fields.count()); + defer gpa.free(field_inits); + + var field_i: u32 = 0; + var extra_index = extra.end; + + while (field_i < extra.data.fields_len) : (field_i += 1) { + const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra_index); + extra_index = item.end; + + const field_type_data = zir_datas[item.data.field_type].pl_node; + const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node }; + const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data; + const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); + const field_index = struct_obj.fields.getIndex(field_name) orelse + return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name); + if (found_fields[field_index] != 0) { + const other_field_type = found_fields[field_index]; + const other_field_type_data = zir_datas[other_field_type].pl_node; + const other_field_src: LazySrcLoc = .{ .node_offset_back2tok = other_field_type_data.src_node }; + const msg = msg: { + const msg = try mod.errMsg(&block.base, field_src, "duplicate field", .{}); + errdefer msg.destroy(gpa); + try mod.errNote(&block.base, other_field_src, msg, "other field here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + } + found_fields[field_index] = item.data.field_type; + field_inits[field_index] = sema.resolveInst(item.data.init); + } - var field_i: u32 = 0; - var extra_index = extra.end; + var root_msg: ?*Module.ErrorMsg = null; - while (field_i < extra.data.fields_len) : (field_i += 1) { - const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra_index); - extra_index = item.end; + for (found_fields) |field_type_inst, i| { + if (field_type_inst != 0) continue; - const field_type_data = zir_datas[item.data.field_type].pl_node; - const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node }; - const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data; - const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); - const field_index = struct_obj.fields.getIndex(field_name) orelse - return sema.failWithBadFieldAccess(block, struct_obj, field_src, field_name); - if (found_fields[field_index] != 0) { - const other_field_type = found_fields[field_index]; - const other_field_type_data = zir_datas[other_field_type].pl_node; - const other_field_src: LazySrcLoc = .{ .node_offset_back2tok = other_field_type_data.src_node }; - const msg = msg: { - const msg = try mod.errMsg(&block.base, field_src, "duplicate field", .{}); - errdefer msg.destroy(gpa); - try mod.errNote(&block.base, other_field_src, msg, "other field here", .{}); - break :msg msg; - }; + // Check if the field has a default init. + const field = struct_obj.fields.values()[i]; + if (field.default_val.tag() == .unreachable_value) { + const field_name = struct_obj.fields.keys()[i]; + const template = "missing struct field: {s}"; + const args = .{field_name}; + if (root_msg) |msg| { + try mod.errNote(&block.base, src, msg, template, args); + } else { + root_msg = try mod.errMsg(&block.base, src, template, args); + } + } else { + field_inits[i] = try sema.addConstant(field.ty, field.default_val); + } + } + if (root_msg) |msg| { + const fqn = try struct_obj.getFullyQualifiedName(gpa); + defer gpa.free(fqn); + try mod.errNoteNonLazy( + struct_obj.srcLoc(), + msg, + "struct '{s}' declared here", + .{fqn}, + ); return mod.failWithOwnedErrorMsg(&block.base, msg); } - found_fields[field_index] = item.data.field_type; - field_inits[field_index] = sema.resolveInst(item.data.init); - } - var root_msg: ?*Module.ErrorMsg = null; + if (is_ref) { + return mod.fail(&block.base, src, "TODO: Sema.zirStructInit is_ref=true", .{}); + } - for (found_fields) |field_type_inst, i| { - if (field_type_inst != 0) continue; - - // Check if the field has a default init. - const field = struct_obj.fields.values()[i]; - if (field.default_val.tag() == .unreachable_value) { - const field_name = struct_obj.fields.keys()[i]; - const template = "missing struct field: {s}"; - const args = .{field_name}; - if (root_msg) |msg| { - try mod.errNote(&block.base, src, msg, template, args); - } else { - root_msg = try mod.errMsg(&block.base, src, template, args); + const is_comptime = for (field_inits) |field_init| { + if (!(try sema.isComptimeKnown(block, src, field_init))) { + break false; } - } else { - field_inits[i] = try sema.addConstant(field.ty, field.default_val); + } else true; + + if (is_comptime) { + const values = try sema.arena.alloc(Value, field_inits.len); + for (field_inits) |field_init, i| { + values[i] = (sema.resolveMaybeUndefVal(block, src, field_init) catch unreachable).?; + } + return sema.addConstant(resolved_ty, try Value.Tag.@"struct".create(sema.arena, values.ptr)); } - } - if (root_msg) |msg| { - const fqn = try struct_obj.getFullyQualifiedName(gpa); - defer gpa.free(fqn); - try mod.errNoteNonLazy( - struct_obj.srcLoc(), - msg, - "struct '{s}' declared here", - .{fqn}, - ); - return mod.failWithOwnedErrorMsg(&block.base, msg); - } - if (is_ref) { - return mod.fail(&block.base, src, "TODO: Sema.zirStructInit is_ref=true", .{}); - } + return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known struct values", .{}); + } else if (resolved_ty.cast(Type.Payload.Union)) |union_payload| { + const union_obj = union_payload.data; + + if (extra.data.fields_len != 1) { + return sema.mod.fail(&block.base, src, "union initialization expects exactly one field", .{}); + } + + const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra.end); + + const field_type_data = zir_datas[item.data.field_type].pl_node; + const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node }; + const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data; + const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); + const field_index = union_obj.fields.getIndex(field_name) orelse + return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); - const is_comptime = for (field_inits) |field_init| { - if (!(try sema.isComptimeKnown(block, src, field_init))) { - break false; + if (is_ref) { + return mod.fail(&block.base, src, "TODO: Sema.zirStructInit is_ref=true union", .{}); } - } else true; - if (is_comptime) { - const values = try sema.arena.alloc(Value, field_inits.len); - for (field_inits) |field_init, i| { - values[i] = (sema.resolveMaybeUndefVal(block, src, field_init) catch unreachable).?; + const init_inst = sema.resolveInst(item.data.init); + if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| { + return sema.addConstant( + resolved_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.int_u64.create(sema.arena, field_index), + .val = val, + }), + ); } - return sema.addConstant(struct_ty, try Value.Tag.@"struct".create(sema.arena, values.ptr)); + return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known union values", .{}); } - - return mod.fail(&block.base, src, "TODO: Sema.zirStructInit for runtime-known struct values", .{}); + unreachable; } fn zirStructInitAnon(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref { @@ -6594,17 +6803,25 @@ fn zirFieldType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE const extra = sema.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data; const src = inst_data.src(); const field_name = sema.code.nullTerminatedString(extra.name_start); - const unresolved_struct_type = try sema.resolveType(block, src, extra.container_type); - if (unresolved_struct_type.zigTypeTag() != .Struct) { - return sema.mod.fail(&block.base, src, "expected struct; found '{}'", .{ - unresolved_struct_type, - }); + const unresolved_ty = try sema.resolveType(block, src, extra.container_type); + const resolved_ty = try sema.resolveTypeFields(block, src, unresolved_ty); + switch (resolved_ty.zigTypeTag()) { + .Struct => { + const struct_obj = resolved_ty.castTag(.@"struct").?.data; + const field = struct_obj.fields.get(field_name) orelse + return sema.failWithBadFieldAccess(block, struct_obj, src, field_name); + return sema.addType(field.ty); + }, + .Union => { + const union_obj = resolved_ty.cast(Type.Payload.Union).?.data; + const field = union_obj.fields.get(field_name) orelse + return sema.failWithBadUnionFieldAccess(block, union_obj, src, field_name); + return sema.addType(field.ty); + }, + else => return sema.mod.fail(&block.base, src, "expected struct or union; found '{}'", .{ + resolved_ty, + }), } - const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_type); - const struct_obj = struct_ty.castTag(.@"struct").?.data; - const field = struct_obj.fields.get(field_name) orelse - return sema.failWithBadFieldAccess(block, struct_obj, src, field_name); - return sema.addType(field.ty); } fn zirErrorReturnTrace( @@ -6679,7 +6896,54 @@ fn zirTagName(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr fn zirReify(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify", .{}); + const type_info_ty = try sema.getBuiltinType(block, src, "TypeInfo"); + const uncasted_operand = sema.resolveInst(inst_data.operand); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src); + const val = try sema.resolveConstValue(block, operand_src, type_info); + const union_val = val.cast(Value.Payload.Union).?.data; + const TypeInfoTag = std.meta.Tag(std.builtin.TypeInfo); + const tag_index = @intCast(std.meta.Tag(TypeInfoTag), union_val.tag.toUnsignedInt()); + switch (@intToEnum(std.builtin.TypeId, tag_index)) { + .Type => return Air.Inst.Ref.type_type, + .Void => return Air.Inst.Ref.void_type, + .Bool => return Air.Inst.Ref.bool_type, + .NoReturn => return Air.Inst.Ref.noreturn_type, + .Int => { + const struct_val = union_val.val.castTag(.@"struct").?.data; + // TODO use reflection instead of magic numbers here + const signedness_val = struct_val[0]; + const bits_val = struct_val[1]; + + const signedness = signedness_val.toEnum(std.builtin.Signedness); + const bits = @intCast(u16, bits_val.toUnsignedInt()); + const ty = switch (signedness) { + .signed => try Type.Tag.int_signed.create(sema.arena, bits), + .unsigned => try Type.Tag.int_unsigned.create(sema.arena, bits), + }; + return sema.addType(ty); + }, + .Float => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Float", .{}), + .Pointer => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Pointer", .{}), + .Array => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Array", .{}), + .Struct => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Struct", .{}), + .ComptimeFloat => return Air.Inst.Ref.comptime_float_type, + .ComptimeInt => return Air.Inst.Ref.comptime_int_type, + .Undefined => return Air.Inst.Ref.undefined_type, + .Null => return Air.Inst.Ref.null_type, + .Optional => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Optional", .{}), + .ErrorUnion => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for ErrorUnion", .{}), + .ErrorSet => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for ErrorSet", .{}), + .Enum => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Enum", .{}), + .Union => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Union", .{}), + .Fn => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Fn", .{}), + .BoundFn => @panic("TODO delete BoundFn from the language"), + .Opaque => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Opaque", .{}), + .Frame => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Frame", .{}), + .AnyFrame => return Air.Inst.Ref.anyframe_type, + .Vector => return sema.mod.fail(&block.base, src, "TODO: Sema.zirReify for Vector", .{}), + .EnumLiteral => return Air.Inst.Ref.enum_literal_type, + } } fn zirTypeName(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -7855,14 +8119,29 @@ fn structFieldPtr( } try sema.requireRuntimeBlock(block, src); + const tag: Air.Inst.Tag = switch (field_index) { + 0 => .struct_field_ptr_index_0, + 1 => .struct_field_ptr_index_1, + 2 => .struct_field_ptr_index_2, + 3 => .struct_field_ptr_index_3, + else => { + return block.addInst(.{ + .tag = .struct_field_ptr, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(ptr_field_ty), + .payload = try sema.addExtra(Air.StructField{ + .struct_operand = struct_ptr, + .field_index = @intCast(u32, field_index), + }), + } }, + }); + }, + }; return block.addInst(.{ - .tag = .struct_field_ptr, - .data = .{ .ty_pl = .{ + .tag = tag, + .data = .{ .ty_op = .{ .ty = try sema.addType(ptr_field_ty), - .payload = try sema.addExtra(Air.StructField{ - .struct_operand = struct_ptr, - .field_index = @intCast(u32, field_index), - }), + .operand = struct_ptr, } }, }); } @@ -8099,24 +8378,35 @@ fn elemPtrArray( elem_index: Air.Inst.Ref, elem_index_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { + const array_ptr_ty = sema.typeOf(array_ptr); + const pointee_type = array_ptr_ty.elemType().elemType(); + const result_ty = if (array_ptr_ty.ptrIsMutable()) + try Type.Tag.single_mut_pointer.create(sema.arena, pointee_type) + else + try Type.Tag.single_const_pointer.create(sema.arena, pointee_type); + if (try sema.resolveDefinedValue(block, src, array_ptr)) |array_ptr_val| { - if (try sema.resolveDefinedValue(block, src, elem_index)) |index_val| { + if (try sema.resolveDefinedValue(block, elem_index_src, elem_index)) |index_val| { // Both array pointer and index are compile-time known. const index_u64 = index_val.toUnsignedInt(); // @intCast here because it would have been impossible to construct a value that // required a larger index. const elem_ptr = try array_ptr_val.elemPtr(sema.arena, @intCast(usize, index_u64)); - const pointee_type = sema.typeOf(array_ptr).elemType().elemType(); - - return sema.addConstant( - try Type.Tag.single_const_pointer.create(sema.arena, pointee_type), - elem_ptr, - ); + return sema.addConstant(result_ty, elem_ptr); } } - _ = elem_index; - _ = elem_index_src; - return sema.mod.fail(&block.base, src, "TODO implement more analyze elemptr for arrays", .{}); + // TODO safety check for array bounds + try sema.requireRuntimeBlock(block, src); + return block.addInst(.{ + .tag = .ptr_elem_ptr, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(result_ty), + .payload = try sema.addExtra(Air.Bin{ + .lhs = array_ptr, + .rhs = elem_index, + }), + } }, + }); } fn coerce( @@ -8323,7 +8613,7 @@ fn coerceNum( return sema.mod.fail(&block.base, inst_src, "TODO float to int", .{}); } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { if (!val.intFitsInType(dest_type, target)) { - return sema.mod.fail(&block.base, inst_src, "type {} cannot represent integer value {}", .{ inst_ty, val }); + return sema.mod.fail(&block.base, inst_src, "type {} cannot represent integer value {}", .{ dest_type, val }); } return try sema.addConstant(dest_type, val); } @@ -8895,7 +9185,7 @@ fn wrapOptional( inst_src: LazySrcLoc, ) !Air.Inst.Ref { if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { - return sema.addConstant(dest_type, val); + return sema.addConstant(dest_type, try Value.Tag.opt_payload.create(sema.arena, val)); } try sema.requireRuntimeBlock(block, inst_src); @@ -9124,22 +9414,62 @@ pub fn resolveTypeLayout( } } -fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type { +/// `sema` and `block` are expected to be the same ones used for the `Decl`. +pub fn resolveDeclFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void { switch (ty.tag()) { .@"struct" => { const struct_obj = ty.castTag(.@"struct").?.data; + if (struct_obj.owner_decl.namespace != sema.owner_decl.namespace) return; switch (struct_obj.status) { .none => {}, .field_types_wip => { return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty}); }, - .have_field_types, .have_layout, .layout_wip => return ty, + .have_field_types, .have_layout, .layout_wip => return, } + const prev_namespace = sema.namespace; + sema.namespace = &struct_obj.namespace; + defer sema.namespace = prev_namespace; + struct_obj.status = .field_types_wip; - try sema.mod.analyzeStructFields(struct_obj); + try sema.analyzeStructFields(block, struct_obj); struct_obj.status = .have_field_types; - return ty; }, + .@"union", .union_tagged => { + const union_obj = ty.cast(Type.Payload.Union).?.data; + if (union_obj.owner_decl.namespace != sema.owner_decl.namespace) return; + switch (union_obj.status) { + .none => {}, + .field_types_wip => { + return sema.mod.fail(&block.base, src, "union {} depends on itself", .{ty}); + }, + .have_field_types, .have_layout, .layout_wip => return, + } + const prev_namespace = sema.namespace; + sema.namespace = &union_obj.namespace; + defer sema.namespace = prev_namespace; + + union_obj.status = .field_types_wip; + try sema.analyzeUnionFields(block, union_obj); + union_obj.status = .have_field_types; + }, + else => return, + } +} + +fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type { + switch (ty.tag()) { + .@"struct" => { + const struct_obj = ty.castTag(.@"struct").?.data; + switch (struct_obj.status) { + .none => unreachable, + .field_types_wip => { + return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty}); + }, + .have_field_types, .have_layout, .layout_wip => return ty, + } + }, + .type_info => return sema.resolveBuiltinTypeFields(block, src, "TypeInfo"), .extern_options => return sema.resolveBuiltinTypeFields(block, src, "ExternOptions"), .export_options => return sema.resolveBuiltinTypeFields(block, src, "ExportOptions"), .atomic_ordering => return sema.resolveBuiltinTypeFields(block, src, "AtomicOrdering"), @@ -9152,18 +9482,12 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type .@"union", .union_tagged => { const union_obj = ty.cast(Type.Payload.Union).?.data; switch (union_obj.status) { - .none => {}, + .none => unreachable, .field_types_wip => { - return sema.mod.fail(&block.base, src, "union {} depends on itself", .{ - ty, - }); + return sema.mod.fail(&block.base, src, "union {} depends on itself", .{ty}); }, .have_field_types, .have_layout, .layout_wip => return ty, } - union_obj.status = .field_types_wip; - try sema.mod.analyzeUnionFields(union_obj); - union_obj.status = .have_field_types; - return ty; }, else => return ty, } @@ -9179,6 +9503,265 @@ fn resolveBuiltinTypeFields( return sema.resolveTypeFields(block, src, resolved_ty); } +fn analyzeStructFields( + sema: *Sema, + block: *Scope.Block, + struct_obj: *Module.Struct, +) CompileError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = sema.gpa; + const zir = sema.code; + const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended; + assert(extended.opcode == .struct_decl); + const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small); + var extra_index: usize = extended.operand; + + const src: LazySrcLoc = .{ .node_offset = struct_obj.node_offset }; + extra_index += @boolToInt(small.has_src_node); + + const body_len = if (small.has_body_len) blk: { + const body_len = zir.extra[extra_index]; + extra_index += 1; + break :blk body_len; + } else 0; + + const fields_len = if (small.has_fields_len) blk: { + const fields_len = zir.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + + const decls_len = if (small.has_decls_len) decls_len: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :decls_len decls_len; + } else 0; + + // Skip over decls. + var decls_it = zir.declIteratorInner(extra_index, decls_len); + while (decls_it.next()) |_| {} + extra_index = decls_it.extra_index; + + const body = zir.extra[extra_index..][0..body_len]; + if (fields_len == 0) { + assert(body.len == 0); + return; + } + extra_index += body.len; + + var decl_arena = struct_obj.owner_decl.value_arena.?.promote(gpa); + defer struct_obj.owner_decl.value_arena.?.* = decl_arena.state; + + try struct_obj.fields.ensureTotalCapacity(&decl_arena.allocator, fields_len); + + if (body.len != 0) { + _ = try sema.analyzeBody(block, body); + } + + const bits_per_field = 4; + const fields_per_u32 = 32 / bits_per_field; + const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; + var bit_bag_index: usize = extra_index; + extra_index += bit_bags_count; + var cur_bit_bag: u32 = undefined; + var field_i: u32 = 0; + while (field_i < fields_len) : (field_i += 1) { + if (field_i % fields_per_u32 == 0) { + cur_bit_bag = zir.extra[bit_bag_index]; + bit_bag_index += 1; + } + const has_align = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const has_default = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const is_comptime = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const unused = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + + _ = unused; + + const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]); + extra_index += 1; + const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); + extra_index += 1; + + // This string needs to outlive the ZIR code. + const field_name = try decl_arena.allocator.dupe(u8, field_name_zir); + const field_ty: Type = if (field_type_ref == .none) + Type.initTag(.noreturn) + else + // TODO: if we need to report an error here, use a source location + // that points to this type expression rather than the struct. + // But only resolve the source location if we need to emit a compile error. + try sema.resolveType(block, src, field_type_ref); + + const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name); + assert(!gop.found_existing); + gop.value_ptr.* = .{ + .ty = try field_ty.copy(&decl_arena.allocator), + .abi_align = Value.initTag(.abi_align_default), + .default_val = Value.initTag(.unreachable_value), + .is_comptime = is_comptime, + .offset = undefined, + }; + + if (has_align) { + const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); + extra_index += 1; + // TODO: if we need to report an error here, use a source location + // that points to this alignment expression rather than the struct. + // But only resolve the source location if we need to emit a compile error. + const abi_align_val = (try sema.resolveInstConst(block, src, align_ref)).val; + gop.value_ptr.abi_align = try abi_align_val.copy(&decl_arena.allocator); + } + if (has_default) { + const default_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); + extra_index += 1; + const default_inst = sema.resolveInst(default_ref); + // TODO: if we need to report an error here, use a source location + // 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 default_val = (try sema.resolveMaybeUndefVal(block, src, default_inst)) orelse + return sema.failWithNeededComptime(block, src); + gop.value_ptr.default_val = try default_val.copy(&decl_arena.allocator); + } + } +} + +fn analyzeUnionFields( + sema: *Sema, + block: *Scope.Block, + union_obj: *Module.Union, +) CompileError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = sema.gpa; + const zir = sema.code; + const extended = zir.instructions.items(.data)[union_obj.zir_index].extended; + assert(extended.opcode == .union_decl); + const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small); + var extra_index: usize = extended.operand; + + const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset }; + extra_index += @boolToInt(small.has_src_node); + + if (small.has_tag_type) { + extra_index += 1; + } + + const body_len = if (small.has_body_len) blk: { + const body_len = zir.extra[extra_index]; + extra_index += 1; + break :blk body_len; + } else 0; + + const fields_len = if (small.has_fields_len) blk: { + const fields_len = zir.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + + const decls_len = if (small.has_decls_len) decls_len: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :decls_len decls_len; + } else 0; + + // Skip over decls. + var decls_it = zir.declIteratorInner(extra_index, decls_len); + while (decls_it.next()) |_| {} + extra_index = decls_it.extra_index; + + const body = zir.extra[extra_index..][0..body_len]; + if (fields_len == 0) { + assert(body.len == 0); + return; + } + extra_index += body.len; + + var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa); + defer union_obj.owner_decl.value_arena.?.* = decl_arena.state; + + try union_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len); + + if (body.len != 0) { + _ = try sema.analyzeBody(block, body); + } + + const bits_per_field = 4; + const fields_per_u32 = 32 / bits_per_field; + const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; + var bit_bag_index: usize = extra_index; + extra_index += bit_bags_count; + var cur_bit_bag: u32 = undefined; + var field_i: u32 = 0; + while (field_i < fields_len) : (field_i += 1) { + if (field_i % fields_per_u32 == 0) { + cur_bit_bag = zir.extra[bit_bag_index]; + bit_bag_index += 1; + } + const has_type = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const has_align = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const has_tag = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const unused = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + _ = unused; + + const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]); + extra_index += 1; + + const field_type_ref: Zir.Inst.Ref = if (has_type) blk: { + const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); + extra_index += 1; + break :blk field_type_ref; + } else .none; + + const align_ref: Zir.Inst.Ref = if (has_align) blk: { + const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); + extra_index += 1; + break :blk align_ref; + } else .none; + + if (has_tag) { + extra_index += 1; + } + + // This string needs to outlive the ZIR code. + const field_name = try decl_arena.allocator.dupe(u8, field_name_zir); + const field_ty: Type = if (field_type_ref == .none) + Type.initTag(.void) + else + // TODO: if we need to report an error here, use a source location + // that points to this type expression rather than the union. + // But only resolve the source location if we need to emit a compile error. + try sema.resolveType(block, src, field_type_ref); + + const gop = union_obj.fields.getOrPutAssumeCapacity(field_name); + assert(!gop.found_existing); + gop.value_ptr.* = .{ + .ty = try field_ty.copy(&decl_arena.allocator), + .abi_align = Value.initTag(.abi_align_default), + }; + + if (align_ref != .none) { + // TODO: if we need to report an error here, use a source location + // that points to this alignment expression rather than the struct. + // But only resolve the source location if we need to emit a compile error. + const abi_align_val = (try sema.resolveInstConst(block, src, align_ref)).val; + gop.value_ptr.abi_align = try abi_align_val.copy(&decl_arena.allocator); + } + } + + // TODO resolve the union tag_type_ref +} + fn getBuiltin( sema: *Sema, block: *Scope.Block, @@ -9291,6 +9874,7 @@ fn typeHasOnePossibleValue( .call_options, .export_options, .extern_options, + .type_info, .@"anyframe", .anyframe_T, .many_const_pointer, @@ -9475,6 +10059,7 @@ pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref { .call_options => return .call_options_type, .export_options => return .export_options_type, .extern_options => return .extern_options_type, + .type_info => return .type_info_type, .manyptr_u8 => return .manyptr_u8_type, .manyptr_const_u8 => return .manyptr_const_u8_type, .fn_noreturn_no_args => return .fn_noreturn_no_args_type, diff --git a/src/TypedValue.zig b/src/TypedValue.zig index 48b2c04970..83242b5329 100644 --- a/src/TypedValue.zig +++ b/src/TypedValue.zig @@ -23,9 +23,18 @@ pub const Managed = struct { }; /// Assumes arena allocation. Does a recursive copy. -pub fn copy(self: TypedValue, allocator: *Allocator) error{OutOfMemory}!TypedValue { +pub fn copy(self: TypedValue, arena: *Allocator) error{OutOfMemory}!TypedValue { return TypedValue{ - .ty = try self.ty.copy(allocator), - .val = try self.val.copy(allocator), + .ty = try self.ty.copy(arena), + .val = try self.val.copy(arena), }; } + +pub fn eql(a: TypedValue, b: TypedValue) bool { + if (!a.ty.eql(b.ty)) return false; + return a.val.eql(b.val, a.ty); +} + +pub fn hash(tv: TypedValue, hasher: *std.hash.Wyhash) void { + return tv.val.hash(tv.ty, hasher); +} diff --git a/src/Zir.zig b/src/Zir.zig index 1aed609de9..2110122580 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -687,14 +687,14 @@ pub const Inst = struct { /// A struct literal with a specified type, with no fields. /// Uses the `un_node` field. struct_init_empty, - /// Given a struct, union, or enum, and a field name as a string index, + /// Given a struct or union, and a field name as a string index, /// returns the field type. Uses the `pl_node` field. Payload is `FieldType`. field_type, - /// Given a struct, union, or enum, and a field name as a Ref, + /// Given a struct or union, and a field name as a Ref, /// returns the field type. Uses the `pl_node` field. Payload is `FieldTypeRef`. field_type_ref, - /// Finalizes a typed struct initialization, performs validation, and returns the - /// struct value. + /// Finalizes a typed struct or union initialization, performs validation, and returns the + /// struct or union value. /// Uses the `pl_node` field. Payload is `StructInit`. struct_init, /// Struct initialization syntax, make the result a pointer. @@ -1703,6 +1703,7 @@ pub const Inst = struct { call_options_type, export_options_type, extern_options_type, + type_info_type, manyptr_u8_type, manyptr_const_u8_type, fn_noreturn_no_args_type, @@ -1973,6 +1974,10 @@ pub const Inst = struct { .ty = Type.initTag(.type), .val = Value.initTag(.extern_options_type), }, + .type_info_type = .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.type_info_type), + }, .undef = .{ .ty = Type.initTag(.@"undefined"), diff --git a/src/codegen.zig b/src/codegen.zig index 77055162b9..a4f6f482b2 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -822,6 +822,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .bit_and => try self.airBitAnd(inst), .bit_or => try self.airBitOr(inst), .xor => try self.airXor(inst), + .shr => try self.airShr(inst), + .shl => try self.airShl(inst), .alloc => try self.airAlloc(inst), .arg => try self.airArg(inst), @@ -853,6 +855,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .store => try self.airStore(inst), .struct_field_ptr=> try self.airStructFieldPtr(inst), .struct_field_val=> try self.airStructFieldVal(inst), + + .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0), + .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1), + .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2), + .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3), + .switch_br => try self.airSwitch(inst), .slice_ptr => try self.airSlicePtr(inst), .slice_len => try self.airSliceLen(inst), @@ -860,6 +868,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), .ptr_elem_val => try self.airPtrElemVal(inst), + .ptr_elem_ptr => try self.airPtrElemPtr(inst), .ptr_ptr_elem_val => try self.airPtrPtrElemVal(inst), .constant => unreachable, // excluded from function bodies @@ -970,6 +979,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { log.debug("%{d} => {}", .{ inst, result }); const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; branch.inst_table.putAssumeCapacityNoClobber(inst, result); + + switch (result) { + .register => |reg| { + // In some cases (such as bitcast), an operand + // may be the same MCValue as the result. If + // that operand died and was a register, it + // was freed by processDeath. We have to + // "re-allocate" the register. + if (self.register_manager.isRegFree(reg)) { + self.register_manager.getRegAssumeFree(reg, inst); + } + }, + else => {}, + } } self.finishAirBookkeeping(); } @@ -1247,6 +1270,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { .arm, .armeb => try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .bit_and), + .x86_64 => try self.genX8664BinMath(inst, bin_op.lhs, bin_op.rhs), else => return self.fail("TODO implement bitwise and for {}", .{self.target.cpu.arch}), }; return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); @@ -1256,6 +1280,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { .arm, .armeb => try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .bit_or), + .x86_64 => try self.genX8664BinMath(inst, bin_op.lhs, bin_op.rhs), else => return self.fail("TODO implement bitwise or for {}", .{self.target.cpu.arch}), }; return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); @@ -1270,6 +1295,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airShl(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + .arm, .armeb => try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .shl), + else => return self.fail("TODO implement shl for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + + fn airShr(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + .arm, .armeb => try self.genArmBinOp(inst, bin_op.lhs, bin_op.rhs, .shr), + else => return self.fail("TODO implement shr for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn airOptionalPayload(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) { @@ -1397,6 +1440,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airPtrElemPtr(self: *Self, inst: Air.Inst.Index) !void { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.Bin, ty_pl.payload).data; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement ptr_elem_ptr for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ extra.lhs, extra.rhs, .none }); + } + fn airPtrPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { const is_volatile = false; // TODO const bin_op = self.air.instructions.items(.data)[inst].bin_op; @@ -1437,7 +1489,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return true; } - fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) !void { + fn load(self: *Self, dst_mcv: MCValue, ptr: MCValue, ptr_ty: Type) InnerError!void { const elem_ty = ptr_ty.elemType(); switch (ptr) { .none => unreachable, @@ -1454,11 +1506,25 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .embedded_in_code => { return self.fail("TODO implement loading from MCValue.embedded_in_code", .{}); }, - .register => { - return self.fail("TODO implement loading from MCValue.register", .{}); + .register => |reg| { + switch (arch) { + .arm, .armeb => switch (dst_mcv) { + .dead => unreachable, + .undef => unreachable, + .compare_flags_signed, .compare_flags_unsigned => unreachable, + .embedded_in_code => unreachable, + .register => |dst_reg| { + writeInt(u32, try self.code.addManyAsArray(4), Instruction.ldr(.al, dst_reg, reg, .{ .offset = Instruction.Offset.none }).toU32()); + }, + else => return self.fail("TODO load from register into {}", .{dst_mcv}), + }, + else => return self.fail("TODO implement loading from MCValue.register for {}", .{arch}), + } }, - .memory => { - return self.fail("TODO implement loading from MCValue.memory", .{}); + .memory => |addr| { + const reg = try self.register_manager.allocReg(null, &.{}); + try self.genSetReg(ptr_ty, reg, .{ .memory = addr }); + try self.load(dst_mcv, .{ .register = reg }, ptr_ty); }, .stack_offset => { return self.fail("TODO implement loading from MCValue.stack_offset", .{}); @@ -1532,7 +1598,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { fn airStructFieldPtr(self: *Self, inst: Air.Inst.Index) !void { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const extra = self.air.extraData(Air.StructField, ty_pl.payload).data; - _ = extra; + return self.structFieldPtr(extra.struct_operand, ty_pl.ty, extra.field_index); + } + + fn airStructFieldPtrIndex(self: *Self, inst: Air.Inst.Index, index: u8) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + return self.structFieldPtr(ty_op.operand, ty_op.ty, index); + } + fn structFieldPtr(self: *Self, operand: Air.Inst.Ref, ty: Air.Inst.Ref, index: u32) !void { + _ = self; + _ = operand; + _ = ty; + _ = index; return self.fail("TODO implement codegen struct_field_ptr", .{}); //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none }); } @@ -1570,15 +1647,53 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } fn genArmBinOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: Air.Inst.Ref, op: Air.Inst.Tag) !MCValue { + // In the case of bitshifts, the type of rhs is different + // from the resulting type + const ty = self.air.typeOf(op_lhs); + + switch (ty.zigTypeTag()) { + .Float => return self.fail("TODO ARM binary operations on floats", .{}), + .Vector => return self.fail("TODO ARM binary operations on vectors", .{}), + .Bool => { + return self.genArmBinIntOp(inst, op_lhs, op_rhs, op, 1, .unsigned); + }, + .Int => { + const int_info = ty.intInfo(self.target.*); + return self.genArmBinIntOp(inst, op_lhs, op_rhs, op, int_info.bits, int_info.signedness); + }, + else => unreachable, + } + } + + fn genArmBinIntOp( + self: *Self, + inst: Air.Inst.Index, + op_lhs: Air.Inst.Ref, + op_rhs: Air.Inst.Ref, + op: Air.Inst.Tag, + bits: u16, + signedness: std.builtin.Signedness, + ) !MCValue { + if (bits > 32) { + return self.fail("TODO ARM binary operations on integers > u32/i32", .{}); + } + const lhs = try self.resolveInst(op_lhs); const rhs = try self.resolveInst(op_rhs); const lhs_is_register = lhs == .register; const rhs_is_register = rhs == .register; - const lhs_should_be_register = try self.armOperandShouldBeRegister(lhs); + const lhs_should_be_register = switch (op) { + .shr, .shl => true, + else => try self.armOperandShouldBeRegister(lhs), + }; const rhs_should_be_register = try self.armOperandShouldBeRegister(rhs); const reuse_lhs = lhs_is_register and self.reuseOperand(inst, op_lhs, 0, lhs); const reuse_rhs = !reuse_lhs and rhs_is_register and self.reuseOperand(inst, op_rhs, 1, rhs); + const can_swap_lhs_and_rhs = switch (op) { + .shr, .shl => false, + else => true, + }; // Destination must be a register var dst_mcv: MCValue = undefined; @@ -1595,7 +1710,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { branch.inst_table.putAssumeCapacity(Air.refToIndex(op_rhs).?, rhs_mcv); } dst_mcv = lhs; - } else if (reuse_rhs) { + } else if (reuse_rhs and can_swap_lhs_and_rhs) { // Allocate 0 or 1 registers if (!lhs_is_register and lhs_should_be_register) { lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(Air.refToIndex(op_lhs).?, &.{rhs.register}) }; @@ -1634,7 +1749,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{}) }; lhs_mcv = dst_mcv; } - } else if (rhs_should_be_register) { + } else if (rhs_should_be_register and can_swap_lhs_and_rhs) { // LHS is immediate if (rhs_is_register) { dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{rhs.register}) }; @@ -1661,6 +1776,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { rhs_mcv, swap_lhs_and_rhs, op, + signedness, ); return dst_mcv; } @@ -1672,6 +1788,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { rhs_mcv: MCValue, swap_lhs_and_rhs: bool, op: Air.Inst.Tag, + signedness: std.builtin.Signedness, ) !void { assert(lhs_mcv == .register or rhs_mcv == .register); @@ -1717,6 +1834,27 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .cmp_eq => { writeInt(u32, try self.code.addManyAsArray(4), Instruction.cmp(.al, op1, operand).toU32()); }, + .shl => { + assert(!swap_lhs_and_rhs); + const shift_amout = switch (operand) { + .Register => |reg_op| Instruction.ShiftAmount.reg(@intToEnum(Register, reg_op.rm)), + .Immediate => |imm_op| Instruction.ShiftAmount.imm(@intCast(u5, imm_op.imm)), + }; + writeInt(u32, try self.code.addManyAsArray(4), Instruction.lsl(.al, dst_reg, op1, shift_amout).toU32()); + }, + .shr => { + assert(!swap_lhs_and_rhs); + const shift_amout = switch (operand) { + .Register => |reg_op| Instruction.ShiftAmount.reg(@intToEnum(Register, reg_op.rm)), + .Immediate => |imm_op| Instruction.ShiftAmount.imm(@intCast(u5, imm_op.imm)), + }; + + const shr = switch (signedness) { + .signed => Instruction.asr, + .unsigned => Instruction.lsr, + }; + writeInt(u32, try self.code.addManyAsArray(4), shr(.al, dst_reg, op1, shift_amout).toU32()); + }, else => unreachable, // not a binary instruction } } @@ -2964,7 +3102,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } // The destination register is not present in the cmp instruction - try self.genArmBinOpCode(undefined, lhs_mcv, rhs_mcv, false, .cmp_eq); + // The signedness of the integer does not matter for the cmp instruction + try self.genArmBinOpCode(undefined, lhs_mcv, rhs_mcv, false, .cmp_eq, undefined); break :result switch (ty.isSignedInt()) { true => MCValue{ .compare_flags_signed = op }, @@ -3787,15 +3926,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { else => return self.fail("TODO implement memset", .{}), } }, - .compare_flags_unsigned => |op| { - _ = op; - return self.fail("TODO implement set stack variable with compare flags value (unsigned)", .{}); - }, - .compare_flags_signed => |op| { - _ = op; - return self.fail("TODO implement set stack variable with compare flags value (signed)", .{}); - }, - .immediate => { + .compare_flags_unsigned, + .compare_flags_signed, + .immediate, + => { const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); }, @@ -3963,15 +4097,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { else => return self.fail("TODO implement memset", .{}), } }, - .compare_flags_unsigned => |op| { - _ = op; - return self.fail("TODO implement set stack variable with compare flags value (unsigned)", .{}); - }, - .compare_flags_signed => |op| { - _ = op; - return self.fail("TODO implement set stack variable with compare flags value (signed)", .{}); - }, - .immediate => { + .compare_flags_unsigned, + .compare_flags_signed, + .immediate, + => { const reg = try self.copyToTmpRegister(ty, mcv); return self.genSetStack(ty, stack_offset, MCValue{ .register = reg }); }, diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig index 891a9e100b..d30479e1f1 100644 --- a/src/codegen/arm.zig +++ b/src/codegen/arm.zig @@ -1142,6 +1142,79 @@ pub const Instruction = union(enum) { return stmdb(cond, .sp, true, @bitCast(RegisterList, register_list)); } } + + pub const ShiftAmount = union(enum) { + immediate: u5, + register: Register, + + pub fn imm(immediate: u5) ShiftAmount { + return .{ + .immediate = immediate, + }; + } + + pub fn reg(register: Register) ShiftAmount { + return .{ + .register = register, + }; + } + }; + + pub fn lsl(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_left))), + .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_left))), + }; + } + + pub fn lsr(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_right))), + .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_right))), + }; + } + + pub fn asr(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .arithmetic_right))), + .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .arithmetic_right))), + }; + } + + pub fn ror(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| mov(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .rotate_right))), + .register => |reg| mov(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .rotate_right))), + }; + } + + pub fn lsls(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_left))), + .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_left))), + }; + } + + pub fn lsrs(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .logical_right))), + .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .logical_right))), + }; + } + + pub fn asrs(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .arithmetic_right))), + .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .arithmetic_right))), + }; + } + + pub fn rors(cond: Condition, rd: Register, rm: Register, shift: ShiftAmount) Instruction { + return switch (shift) { + .immediate => |imm| movs(cond, rd, Operand.reg(rm, Operand.Shift.imm(imm, .rotate_right))), + .register => |reg| movs(cond, rd, Operand.reg(rm, Operand.Shift.reg(reg, .rotate_right))), + }; + } }; test "serialize instructions" { @@ -1262,6 +1335,20 @@ test "aliases" { .actual = Instruction.push(.al, .{ .r0, .r2 }), .expected = Instruction.stmdb(.al, .sp, true, .{ .r0 = true, .r2 = true }), }, + .{ // lsl r4, r5, #5 + .actual = Instruction.lsl(.al, .r4, .r5, Instruction.ShiftAmount.imm(5)), + .expected = Instruction.mov(.al, .r4, Instruction.Operand.reg( + .r5, + Instruction.Operand.Shift.imm(5, .logical_left), + )), + }, + .{ // asrs r1, r1, r3 + .actual = Instruction.asrs(.al, .r1, .r1, Instruction.ShiftAmount.reg(.r3)), + .expected = Instruction.movs(.al, .r1, Instruction.Operand.reg( + .r1, + Instruction.Operand.Shift.reg(.r3, .arithmetic_right), + )), + }, }; for (testcases) |case| { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a67e2438c2..2084b1e1ce 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -319,18 +319,20 @@ pub const DeclGen = struct { .Bool => return writer.print("{}", .{val.toBool()}), .Optional => { var opt_buf: Type.Payload.ElemType = undefined; - const child_type = t.optionalChild(&opt_buf); + const payload_type = t.optionalChild(&opt_buf); if (t.isPtrLikeOptional()) { - return dg.renderValue(writer, child_type, val); + return dg.renderValue(writer, payload_type, val); } try writer.writeByte('('); try dg.renderType(writer, t); - if (val.tag() == .null_value) { - try writer.writeAll("){ .is_null = true }"); - } else { - try writer.writeAll("){ .is_null = false, .payload = "); - try dg.renderValue(writer, child_type, val); + try writer.writeAll("){"); + if (val.castTag(.opt_payload)) |pl| { + const payload_val = pl.data; + try writer.writeAll(" .is_null = false, .payload = "); + try dg.renderValue(writer, payload_type, payload_val); try writer.writeAll(" }"); + } else { + try writer.writeAll(" .is_null = true }"); } }, .ErrorSet => { @@ -871,6 +873,9 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .bit_or => try airBinOp(o, inst, " | "), .xor => try airBinOp(o, inst, " ^ "), + .shr => try airBinOp(o, inst, " >> "), + .shl => try airBinOp(o, inst, " << "), + .not => try airNot( o, inst), .optional_payload => try airOptionalPayload(o, inst), @@ -904,12 +909,19 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .switch_br => try airSwitchBr(o, inst), .wrap_optional => try airWrapOptional(o, inst), .struct_field_ptr => try airStructFieldPtr(o, inst), + + .struct_field_ptr_index_0 => try airStructFieldPtrIndex(o, inst, 0), + .struct_field_ptr_index_1 => try airStructFieldPtrIndex(o, inst, 1), + .struct_field_ptr_index_2 => try airStructFieldPtrIndex(o, inst, 2), + .struct_field_ptr_index_3 => try airStructFieldPtrIndex(o, inst, 3), + .struct_field_val => try airStructFieldVal(o, inst), .slice_ptr => try airSliceField(o, inst, ".ptr;\n"), .slice_len => try airSliceField(o, inst, ".len;\n"), .ptr_elem_val => try airPtrElemVal(o, inst, "["), .ptr_ptr_elem_val => try airPtrElemVal(o, inst, "[0]["), + .ptr_elem_ptr => try airPtrElemPtr(o, inst), .slice_elem_val => try airSliceElemVal(o, inst, "["), .ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["), @@ -957,6 +969,13 @@ fn airPtrElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { return o.dg.fail("TODO: C backend: airPtrElemVal", .{}); } +fn airPtrElemPtr(o: *Object, inst: Air.Inst.Index) !CValue { + if (o.liveness.isUnused(inst)) + return CValue.none; + + return o.dg.fail("TODO: C backend: airPtrElemPtr", .{}); +} + fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { const is_volatile = false; // TODO if (!is_volatile and o.liveness.isUnused(inst)) @@ -1638,15 +1657,31 @@ fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue { fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue { if (o.liveness.isUnused(inst)) - return CValue.none; + // TODO this @as is needed because of a stage1 bug + return @as(CValue, CValue.none); const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; const extra = o.air.extraData(Air.StructField, ty_pl.payload).data; - const writer = o.writer(); const struct_ptr = try o.resolveInst(extra.struct_operand); const struct_ptr_ty = o.air.typeOf(extra.struct_operand); + return structFieldPtr(o, inst, struct_ptr_ty, struct_ptr, extra.field_index); +} + +fn airStructFieldPtrIndex(o: *Object, inst: Air.Inst.Index, index: u8) !CValue { + if (o.liveness.isUnused(inst)) + // TODO this @as is needed because of a stage1 bug + return @as(CValue, CValue.none); + + const ty_op = o.air.instructions.items(.data)[inst].ty_op; + const struct_ptr = try o.resolveInst(ty_op.operand); + const struct_ptr_ty = o.air.typeOf(ty_op.operand); + return structFieldPtr(o, inst, struct_ptr_ty, struct_ptr, index); +} + +fn structFieldPtr(o: *Object, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue { + const writer = o.writer(); const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data; - const field_name = struct_obj.fields.keys()[extra.field_index]; + const field_name = struct_obj.fields.keys()[index]; const inst_ty = o.air.typeOfIndex(inst); const local = try o.allocLocal(inst_ty, .Const); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4f9a24f80c..d7aa2d45b3 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -432,6 +432,8 @@ pub const Object = struct { }, else => |e| return e, }; + const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; + try self.updateDeclExports(module, decl, decl_exports); } pub fn updateDeclExports( @@ -440,7 +442,9 @@ pub const Object = struct { decl: *const Module.Decl, exports: []const *Module.Export, ) !void { - const llvm_fn = self.llvm_module.getNamedFunction(decl.name).?; + // If the module does not already have the function, we ignore this function call + // because we call `updateDeclExports` at the end of `updateFunc` and `updateDecl`. + const llvm_fn = self.llvm_module.getNamedFunction(decl.name) orelse return; const is_extern = decl.val.tag() == .extern_fn; if (is_extern or exports.len != 0) { llvm_fn.setLinkage(.External); @@ -806,27 +810,22 @@ pub const DeclGen = struct { return self.todo("handle more array values", .{}); }, .Optional => { - if (!tv.ty.isPtrLikeOptional()) { - var buf: Type.Payload.ElemType = undefined; - const child_type = tv.ty.optionalChild(&buf); - const llvm_child_type = try self.llvmType(child_type); - - if (tv.val.tag() == .null_value) { - var optional_values: [2]*const llvm.Value = .{ - llvm_child_type.constNull(), - self.context.intType(1).constNull(), - }; - 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 }), - self.context.intType(1).constAllOnes(), - }; - return self.context.constStruct(&optional_values, optional_values.len, .False); - } - } else { + if (tv.ty.isPtrLikeOptional()) { return self.todo("implement const of optional pointer", .{}); } + var buf: Type.Payload.ElemType = undefined; + const payload_type = tv.ty.optionalChild(&buf); + const is_pl = !tv.val.isNull(); + const llvm_i1 = self.context.intType(1); + + const fields: [2]*const llvm.Value = .{ + try self.genTypedValue(.{ + .ty = payload_type, + .val = if (tv.val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef), + }), + if (is_pl) llvm_i1.constAllOnes() else llvm_i1.constNull(), + }; + return self.context.constStruct(&fields, fields.len, .False); }, .Fn => { const fn_decl = switch (tv.val.tag()) { @@ -993,6 +992,9 @@ pub const FuncGen = struct { .bit_or, .bool_or => try self.airOr(inst), .xor => try self.airXor(inst), + .shl => try self.airShl(inst), + .shr => try self.airShr(inst), + .cmp_eq => try self.airCmp(inst, .eq), .cmp_gt => try self.airCmp(inst, .gt), .cmp_gte => try self.airCmp(inst, .gte), @@ -1035,9 +1037,15 @@ pub const FuncGen = struct { .struct_field_ptr => try self.airStructFieldPtr(inst), .struct_field_val => try self.airStructFieldVal(inst), + .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0), + .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1), + .struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2), + .struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3), + .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), .ptr_elem_val => try self.airPtrElemVal(inst), + .ptr_elem_ptr => try self.airPtrElemPtr(inst), .ptr_ptr_elem_val => try self.airPtrPtrElemVal(inst), .optional_payload => try self.airOptionalPayload(inst, false), @@ -1293,11 +1301,35 @@ pub const FuncGen = struct { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const base_ptr = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); - const indices: [1]*const llvm.Value = .{rhs}; - const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + const ptr = if (self.air.typeOf(bin_op.lhs).isSinglePointer()) ptr: { + // If this is a single-item pointer to an array, we need another index in the GEP. + const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs }; + break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } else ptr: { + const indices: [1]*const llvm.Value = .{rhs}; + break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + }; return self.builder.buildLoad(ptr, ""); } + fn airPtrElemPtr(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 bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; + const base_ptr = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + if (self.air.typeOf(bin_op.lhs).isSinglePointer()) { + // If this is a single-item pointer to an array, we need another index in the GEP. + const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs }; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } else { + const indices: [1]*const llvm.Value = .{rhs}; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } + } + fn airPtrPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const is_volatile = false; // TODO if (!is_volatile and self.liveness.isUnused(inst)) @@ -1323,6 +1355,15 @@ pub const FuncGen = struct { return self.builder.buildStructGEP(struct_ptr, field_index, ""); } + fn airStructFieldPtrIndex(self: *FuncGen, inst: Air.Inst.Index, field_index: c_uint) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const struct_ptr = try self.resolveInst(ty_op.operand); + return self.builder.buildStructGEP(struct_ptr, field_index, ""); + } + fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1736,6 +1777,41 @@ pub const FuncGen = struct { return self.builder.buildXor(lhs, rhs, ""); } + fn airShl(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 lhs_type = self.air.typeOf(bin_op.lhs); + const tg = self.dg.module.getTarget(); + const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg)) + self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "") + else + rhs; + return self.builder.buildShl(lhs, casted_rhs, ""); + } + + fn airShr(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 lhs_type = self.air.typeOf(bin_op.lhs); + const tg = self.dg.module.getTarget(); + const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg)) + self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "") + else + rhs; + + if (self.air.typeOfIndex(inst).isSignedInt()) { + return self.builder.buildAShr(lhs, casted_rhs, ""); + } else { + return self.builder.buildLShr(lhs, casted_rhs, ""); + } + } + fn airIntCast(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 da02e56ac8..d33ca29d4f 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -290,6 +290,14 @@ pub const Builder = opaque { pub const getInsertBlock = LLVMGetInsertBlock; extern fn LLVMGetInsertBlock(Builder: *const Builder) *const BasicBlock; + pub const buildZExt = LLVMBuildZExt; + extern fn LLVMBuildZExt( + *const Builder, + Value: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; + pub const buildCall = LLVMBuildCall; extern fn LLVMBuildCall( *const Builder, @@ -381,6 +389,15 @@ pub const Builder = opaque { pub const buildAnd = LLVMBuildAnd; extern fn LLVMBuildAnd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildLShr = LLVMBuildLShr; + extern fn LLVMBuildLShr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildAShr = LLVMBuildAShr; + extern fn LLVMBuildAShr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildShl = LLVMBuildShl; + extern fn LLVMBuildShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildOr = LLVMBuildOr; extern fn LLVMBuildOr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 4814ba0b55..bb05567236 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -862,6 +862,10 @@ pub const Context = struct { .ret => self.airRet(inst), .store => self.airStore(inst), .struct_field_ptr => self.airStructFieldPtr(inst), + .struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0), + .struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1), + .struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2), + .struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3), .switch_br => self.airSwitchBr(inst), .unreach => self.airUnreachable(inst), .wrap_optional => self.airWrapOptional(inst), @@ -1198,7 +1202,12 @@ pub const Context = struct { // When constant has value 'null', set is_null local to '1' // and payload to '0' - if (val.tag() == .null_value) { + if (val.castTag(.opt_payload)) |pl| { + const payload_val = pl.data; + try writer.writeByte(wasm.opcode(.i32_const)); + try leb.writeILEB128(writer, @as(i32, 0)); + try self.emitConstant(payload_val, payload_type); + } else { try writer.writeByte(wasm.opcode(.i32_const)); try leb.writeILEB128(writer, @as(i32, 1)); @@ -1208,10 +1217,6 @@ pub const Context = struct { }); try writer.writeByte(wasm.opcode(opcode)); try leb.writeULEB128(writer, @as(u32, 0)); - } else { - try writer.writeByte(wasm.opcode(.i32_const)); - try leb.writeILEB128(writer, @as(i32, 0)); - try self.emitConstant(val, payload_type); } }, else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}), @@ -1440,8 +1445,15 @@ pub const Context = struct { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const extra = self.air.extraData(Air.StructField, ty_pl.payload); const struct_ptr = self.resolveInst(extra.data.struct_operand); - - return WValue{ .local = struct_ptr.multi_value.index + @intCast(u32, extra.data.field_index) }; + return structFieldPtr(struct_ptr, extra.data.field_index); + } + fn airStructFieldPtrIndex(self: *Context, inst: Air.Inst.Index, index: u32) InnerError!WValue { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const struct_ptr = self.resolveInst(ty_op.operand); + return structFieldPtr(struct_ptr, index); + } + fn structFieldPtr(struct_ptr: WValue, index: u32) InnerError!WValue { + return WValue{ .local = struct_ptr.multi_value.index + index }; } fn airSwitchBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 3c3cd4eef3..fc559948c4 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -765,12 +765,8 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { if (self.base.options.wasi_exec_model == .reactor) { // Reactor execution model does not have _start so lld doesn't look for it. try argv.append("--no-entry"); - // Make sure "_initialize" is exported even if this is pure Zig WASI reactor - // where WASM_SYMBOL_EXPORTED flag in LLVM is not set on _initialize. - try argv.appendSlice(&[_][]const u8{ - "--export", - "_initialize", - }); + // Make sure "_initialize" and other used-defined functions are exported if this is WASI reactor. + try argv.append("--export-dynamic"); } } else { try argv.append("--no-entry"); // So lld doesn't look for _start. diff --git a/src/main.zig b/src/main.zig index 285b6d2316..44b3a0515c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2492,6 +2492,7 @@ fn cmdTranslateC(comp: *Compilation, arena: *Allocator, enable_cache: bool) !voi const digest = if (try man.hit()) man.final() else digest: { var argv = std.ArrayList([]const u8).init(arena); + try argv.append(""); // argv[0] is program name, actual args start at [1] var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{}); defer zig_cache_tmp_dir.close(); diff --git a/src/mingw.zig b/src/mingw.zig index 42d1ac47db..529025c517 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -187,27 +187,25 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { }; } } else if (target.cpu.arch.isARM()) { - if (target.cpu.arch.ptrBitWidth() == 32) { - for (mingwex_arm32_src) |dep| { - (try c_source_files.addOne()).* = .{ - .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ - "libc", "mingw", dep, - }), - .extra_flags = extra_flags, - }; - } - } else { - for (mingwex_arm64_src) |dep| { - (try c_source_files.addOne()).* = .{ - .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ - "libc", "mingw", dep, - }), - .extra_flags = extra_flags, - }; - } + for (mingwex_arm32_src) |dep| { + (try c_source_files.addOne()).* = .{ + .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ + "libc", "mingw", dep, + }), + .extra_flags = extra_flags, + }; + } + } else if (target.cpu.arch.isAARCH64()) { + for (mingwex_arm64_src) |dep| { + (try c_source_files.addOne()).* = .{ + .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ + "libc", "mingw", dep, + }), + .extra_flags = extra_flags, + }; } } else { - unreachable; + @panic("unsupported arch"); } return comp.build_crt_file("mingwex", .Lib, c_source_files.items); }, diff --git a/src/print_air.zig b/src/print_air.zig index 66490b6512..276158f720 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -127,6 +127,8 @@ const Writer = struct { .ptr_slice_elem_val, .ptr_elem_val, .ptr_ptr_elem_val, + .shl, + .shr, => try w.writeBinOp(s, inst), .is_null, @@ -167,12 +169,17 @@ const Writer = struct { .wrap_errunion_err, .slice_ptr, .slice_len, + .struct_field_ptr_index_0, + .struct_field_ptr_index_1, + .struct_field_ptr_index_2, + .struct_field_ptr_index_3, => try w.writeTyOp(s, inst), .block, .loop, => try w.writeBlock(s, inst), + .ptr_elem_ptr => try w.writePtrElemPtr(s, inst), .struct_field_ptr => try w.writeStructField(s, inst), .struct_field_val => try w.writeStructField(s, inst), .constant => try w.writeConstant(s, inst), @@ -237,10 +244,19 @@ const Writer = struct { fn writeStructField(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; - const extra = w.air.extraData(Air.StructField, ty_pl.payload); + const extra = w.air.extraData(Air.StructField, ty_pl.payload).data; - try w.writeOperand(s, inst, 0, extra.data.struct_operand); - try s.print(", {d}", .{extra.data.field_index}); + try w.writeOperand(s, inst, 0, extra.struct_operand); + try s.print(", {d}", .{extra.field_index}); + } + + fn writePtrElemPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; + const extra = w.air.extraData(Air.Bin, ty_pl.payload).data; + + try w.writeOperand(s, inst, 0, extra.lhs); + try s.writeAll(", "); + try w.writeOperand(s, inst, 0, extra.rhs); } fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index cf61bf79b7..402c86d86f 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -4171,46 +4171,11 @@ ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf } else { variable_entry->align_bytes = get_abi_alignment(g, var_type); - ZigVar *existing_var = find_variable(g, parent_scope, name, nullptr); - if (existing_var && !existing_var->shadowable) { - if (existing_var->var_type == nullptr || !type_is_invalid(existing_var->var_type)) { - ErrorMsg *msg = add_node_error(g, source_node, - buf_sprintf("redeclaration of variable '%s'", buf_ptr(name))); - add_error_note(g, msg, existing_var->decl_node, buf_sprintf("previous declaration here")); - } + ZigType *type; + if (get_primitive_type(g, name, &type) != ErrorPrimitiveTypeNotFound) { + add_node_error(g, source_node, + buf_sprintf("variable shadows primitive type '%s'", buf_ptr(name))); variable_entry->var_type = g->builtin_types.entry_invalid; - } else { - ZigType *type; - if (get_primitive_type(g, name, &type) != ErrorPrimitiveTypeNotFound) { - add_node_error(g, source_node, - buf_sprintf("variable shadows primitive type '%s'", buf_ptr(name))); - variable_entry->var_type = g->builtin_types.entry_invalid; - } else { - Scope *search_scope = nullptr; - if (src_tld == nullptr) { - search_scope = parent_scope; - } else if (src_tld->parent_scope != nullptr && src_tld->parent_scope->parent != nullptr) { - search_scope = src_tld->parent_scope->parent; - } - if (search_scope != nullptr) { - Tld *tld = find_decl(g, search_scope, name); - if (tld != nullptr && tld != src_tld) { - bool want_err_msg = true; - if (tld->id == TldIdVar) { - ZigVar *var = reinterpret_cast<TldVar *>(tld)->var; - if (var != nullptr && var->var_type != nullptr && type_is_invalid(var->var_type)) { - want_err_msg = false; - } - } - if (want_err_msg) { - ErrorMsg *msg = add_node_error(g, source_node, - buf_sprintf("redefinition of '%s'", buf_ptr(name))); - add_error_note(g, msg, tld->source_node, buf_sprintf("previous definition here")); - } - variable_entry->var_type = g->builtin_types.entry_invalid; - } - } - } } } diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp index 44dc1080c2..86c18abc1e 100644 --- a/src/stage1/astgen.cpp +++ b/src/stage1/astgen.cpp @@ -3200,23 +3200,6 @@ ZigVar *create_local_var(CodeGen *codegen, AstNode *node, Scope *parent_scope, add_node_error(codegen, node, buf_sprintf("variable shadows primitive type '%s'", buf_ptr(name))); variable_entry->var_type = codegen->builtin_types.entry_invalid; - } else { - Tld *tld = find_decl(codegen, parent_scope, name); - if (tld != nullptr) { - bool want_err_msg = true; - if (tld->id == TldIdVar) { - ZigVar *var = reinterpret_cast<TldVar *>(tld)->var; - if (var != nullptr && var->var_type != nullptr && type_is_invalid(var->var_type)) { - want_err_msg = false; - } - } - if (want_err_msg) { - ErrorMsg *msg = add_node_error(codegen, node, - buf_sprintf("redefinition of '%s'", buf_ptr(name))); - add_error_note(codegen, msg, tld->source_node, buf_sprintf("previous definition here")); - } - variable_entry->var_type = codegen->builtin_types.entry_invalid; - } } } } @@ -3875,7 +3858,31 @@ static Stage1ZirInst *astgen_identifier(Stage1AstGen *ag, Scope *scope, AstNode } } - Tld *tld = find_decl(ag->codegen, scope, variable_name); + Tld *tld = nullptr; + { + Scope *s = scope; + while (s) { + if (s->id == ScopeIdDecls) { + ScopeDecls *decls_scope = (ScopeDecls *)s; + + Tld *result = find_container_decl(ag->codegen, decls_scope, variable_name); + if (result != nullptr) { + if (tld != nullptr && tld != result) { + ErrorMsg *msg = add_node_error(ag->codegen, node, + buf_sprintf("ambiguous reference")); + add_error_note(ag->codegen, msg, tld->source_node, + buf_sprintf("declared here")); + add_error_note(ag->codegen, msg, result->source_node, + buf_sprintf("also declared here")); + return ag->codegen->invalid_inst_src; + } + tld = result; + } + } + s = s->parent; + } + } + if (tld) { Stage1ZirInst *decl_ref = ir_build_decl_ref(ag, scope, node, tld, lval); if (lval == LValPtr || lval == LValAssign) { @@ -4653,17 +4660,17 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast AstNode *arg1_node = node->data.fn_call_expr.params.at(1); Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope); - if (arg0_value == ag->codegen->invalid_inst_src) + if (arg1_value == ag->codegen->invalid_inst_src) return arg1_value; AstNode *arg2_node = node->data.fn_call_expr.params.at(2); Stage1ZirInst *arg2_value = astgen_node(ag, arg2_node, scope); - if (arg1_value == ag->codegen->invalid_inst_src) + if (arg2_value == ag->codegen->invalid_inst_src) return arg2_value; AstNode *arg3_node = node->data.fn_call_expr.params.at(3); Stage1ZirInst *arg3_value = astgen_node(ag, arg3_node, scope); - if (arg2_value == ag->codegen->invalid_inst_src) + if (arg3_value == ag->codegen->invalid_inst_src) return arg3_value; Stage1ZirInst *select = ir_build_select(ag, scope, node, diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 359af18e82..c44081c770 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -3831,10 +3831,14 @@ static LLVMValueRef ir_render_load_ptr(CodeGen *g, Stage1Air *executable, LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false); LLVMValueRef shifted_value = LLVMBuildLShr(g->builder, containing_int, shift_amt_val, ""); + LLVMTypeRef same_size_int = LLVMIntType(size_in_bits); + LLVMValueRef mask = LLVMConstAllOnes(LLVMIntType(size_in_bits)); + mask = LLVMConstZExt(mask, LLVMTypeOf(containing_int)); + LLVMValueRef masked_value = LLVMBuildAnd(g->builder, shifted_value, mask, ""); + if (handle_is_ptr(g, child_type)) { LLVMValueRef result_loc = ir_llvm_value(g, instruction->result_loc); - LLVMTypeRef same_size_int = LLVMIntType(size_in_bits); - LLVMValueRef truncated_int = LLVMBuildTrunc(g->builder, shifted_value, same_size_int, ""); + LLVMValueRef truncated_int = LLVMBuildTrunc(g->builder, masked_value, same_size_int, ""); LLVMValueRef bitcasted_ptr = LLVMBuildBitCast(g->builder, result_loc, LLVMPointerType(same_size_int, 0), ""); LLVMBuildStore(g->builder, truncated_int, bitcasted_ptr); @@ -3842,12 +3846,11 @@ static LLVMValueRef ir_render_load_ptr(CodeGen *g, Stage1Air *executable, } if (child_type->id == ZigTypeIdFloat) { - LLVMTypeRef same_size_int = LLVMIntType(size_in_bits); - LLVMValueRef truncated_int = LLVMBuildTrunc(g->builder, shifted_value, same_size_int, ""); + LLVMValueRef truncated_int = LLVMBuildTrunc(g->builder, masked_value, same_size_int, ""); return LLVMBuildBitCast(g->builder, truncated_int, get_llvm_type(g, child_type), ""); } - return LLVMBuildTrunc(g->builder, shifted_value, get_llvm_type(g, child_type), ""); + return LLVMBuildTrunc(g->builder, masked_value, get_llvm_type(g, child_type), ""); } static bool value_is_all_undef_array(CodeGen *g, ZigValue *const_val, size_t len) { diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index b1583cc6b4..830ce76708 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -20007,29 +20007,24 @@ static Stage1AirInst *ir_analyze_instruction_truncate(IrAnalyze *ira, Stage1ZirI return ir_build_truncate_gen(ira, instruction->base.scope, instruction->base.source_node, dest_type, target); } -static Stage1AirInst *ir_analyze_instruction_int_cast(IrAnalyze *ira, Stage1ZirInstIntCast *instruction) { - ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child); - if (type_is_invalid(dest_type)) - return ira->codegen->invalid_inst_gen; - +static Stage1AirInst *ir_analyze_int_cast(IrAnalyze *ira, Scope *scope, AstNode *source_node, + ZigType *dest_type, AstNode *dest_type_src_node, + Stage1AirInst *target, AstNode *target_src_node) +{ ZigType *scalar_dest_type = (dest_type->id == ZigTypeIdVector) ? dest_type->data.vector.elem_type : dest_type; if (scalar_dest_type->id != ZigTypeIdInt && scalar_dest_type->id != ZigTypeIdComptimeInt) { - ir_add_error_node(ira, instruction->dest_type->source_node, + ir_add_error_node(ira, dest_type_src_node, buf_sprintf("expected integer type, found '%s'", buf_ptr(&scalar_dest_type->name))); return ira->codegen->invalid_inst_gen; } - Stage1AirInst *target = instruction->target->child; - if (type_is_invalid(target->value->type)) - return ira->codegen->invalid_inst_gen; - ZigType *scalar_target_type = (target->value->type->id == ZigTypeIdVector) ? target->value->type->data.vector.elem_type : target->value->type; if (scalar_target_type->id != ZigTypeIdInt && scalar_target_type->id != ZigTypeIdComptimeInt) { - ir_add_error_node(ira, instruction->target->source_node, buf_sprintf("expected integer type, found '%s'", + ir_add_error_node(ira, target_src_node, buf_sprintf("expected integer type, found '%s'", buf_ptr(&scalar_target_type->name))); return ira->codegen->invalid_inst_gen; } @@ -20039,10 +20034,24 @@ static Stage1AirInst *ir_analyze_instruction_int_cast(IrAnalyze *ira, Stage1ZirI if (val == nullptr) return ira->codegen->invalid_inst_gen; - return ir_implicit_cast2(ira, instruction->target->scope, instruction->target->source_node, target, dest_type); + return ir_implicit_cast2(ira, scope, target_src_node, target, dest_type); } - return ir_analyze_widen_or_shorten(ira, instruction->base.scope, instruction->base.source_node, target, dest_type); + return ir_analyze_widen_or_shorten(ira, scope, source_node, target, dest_type); +} + +static Stage1AirInst *ir_analyze_instruction_int_cast(IrAnalyze *ira, Stage1ZirInstIntCast *instruction) { + ZigType *dest_type = ir_resolve_type(ira, instruction->dest_type->child); + if (type_is_invalid(dest_type)) + return ira->codegen->invalid_inst_gen; + + Stage1AirInst *target = instruction->target->child; + if (type_is_invalid(target->value->type)) + return ira->codegen->invalid_inst_gen; + + return ir_analyze_int_cast(ira, instruction->base.scope, instruction->base.source_node, + dest_type, instruction->dest_type->source_node, + target, instruction->target->source_node); } static Stage1AirInst *ir_analyze_instruction_float_cast(IrAnalyze *ira, Stage1ZirInstFloatCast *instruction) { @@ -24282,7 +24291,9 @@ static Stage1AirInst *ir_analyze_instruction_int_to_enum(IrAnalyze *ira, Stage1Z if (type_is_invalid(target->value->type)) return ira->codegen->invalid_inst_gen; - Stage1AirInst *casted_target = ir_implicit_cast(ira, target, tag_type); + Stage1AirInst *casted_target = ir_analyze_int_cast(ira, instruction->base.scope, + instruction->base.source_node, tag_type, instruction->dest_type->source_node, + target, instruction->target->source_node); if (type_is_invalid(casted_target->value->type)) return ira->codegen->invalid_inst_gen; diff --git a/src/stage1/target.cpp b/src/stage1/target.cpp index 6594eb57d5..8415b1934e 100644 --- a/src/stage1/target.cpp +++ b/src/stage1/target.cpp @@ -919,7 +919,7 @@ bool target_has_valgrind_support(const ZigTarget *target) { case ZigLLVM_UnknownArch: zig_unreachable(); case ZigLLVM_x86_64: - return (target->os == OsLinux || target_os_is_darwin(target->os) || target->os == OsSolaris || + return (target->os == OsLinux || target->os == OsSolaris || (target->os == OsWindows && target->abi != ZigLLVM_MSVC)); default: return false; diff --git a/src/target.zig b/src/target.zig index 06483d68ff..25a133f03b 100644 --- a/src/target.zig +++ b/src/target.zig @@ -163,7 +163,7 @@ pub fn isSingleThreaded(target: std.Target) bool { pub fn hasValgrindSupport(target: std.Target) bool { switch (target.cpu.arch) { .x86_64 => { - return target.os.tag == .linux or target.isDarwin() or target.os.tag == .solaris or + return target.os.tag == .linux or target.os.tag == .solaris or (target.os.tag == .windows and target.abi != .msvc); }, else => return false, diff --git a/src/translate_c.zig b/src/translate_c.zig index e11fc5b736..0cc40cdfd4 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -719,6 +719,30 @@ fn transQualTypeMaybeInitialized(c: *Context, scope: *Scope, qt: clang.QualType, transQualType(c, scope, qt, loc); } +/// This is used in global scope to convert a string literal `S` to [*c]u8: +/// &(struct { +/// var static = S.*; +/// }).static; +fn stringLiteralToCharStar(c: *Context, str: Node) Error!Node { + const var_name = Scope.Block.StaticInnerName; + + const variables = try c.arena.alloc(Node, 1); + variables[0] = try Tag.mut_str.create(c.arena, .{ .name = var_name, .init = str }); + + const anon_struct = try Tag.@"struct".create(c.arena, .{ + .layout = .none, + .fields = &.{}, + .functions = &.{}, + .variables = variables, + }); + + const member_access = try Tag.field_access.create(c.arena, .{ + .lhs = anon_struct, + .field_name = var_name, + }); + return Tag.address_of.create(c.arena, member_access); +} + /// if mangled_name is not null, this var decl was declared in a block scope. fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]const u8) Error!void { const var_name = mangled_name orelse try c.str(@ptrCast(*const clang.NamedDecl, var_decl).getName_bytes_begin()); @@ -779,6 +803,8 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co }; if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node.?)) { init_node = try Tag.bool_to_int.create(c.arena, init_node.?); + } else if (init_node.?.tag() == .string_literal and qualTypeIsCharStar(qual_type)) { + init_node = try stringLiteralToCharStar(c, init_node.?); } } else { init_node = Tag.undefined_literal.init(); @@ -1101,9 +1127,10 @@ fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordD record_payload.* = .{ .base = .{ .tag = ([2]Tag{ .@"struct", .@"union" })[@boolToInt(is_union)] }, .data = .{ - .is_packed = is_packed, + .layout = if (is_packed) .@"packed" else .@"extern", .fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items), .functions = try c.arena.dupe(Node, functions.items), + .variables = &.{}, }, }; break :blk Node.initPayload(&record_payload.base); @@ -1805,6 +1832,9 @@ fn transDeclStmtOne( Tag.undefined_literal.init(); if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node)) { init_node = try Tag.bool_to_int.create(c.arena, init_node); + } else if (init_node.tag() == .string_literal and qualTypeIsCharStar(qual_type)) { + const dst_type_node = try transQualType(c, scope, qual_type, loc); + init_node = try removeCVQualifiers(c, dst_type_node, init_node); } const var_name: []const u8 = if (is_static_local) Scope.Block.StaticInnerName else mangled_name; @@ -2522,9 +2552,19 @@ fn transInitListExprRecord( raw_name = try mem.dupe(c.arena, u8, name); } + var init_expr = try transExpr(c, scope, elem_expr, .used); + const field_qt = field_decl.getType(); + if (init_expr.tag() == .string_literal and qualTypeIsCharStar(field_qt)) { + if (scope.id == .root) { + init_expr = try stringLiteralToCharStar(c, init_expr); + } else { + const dst_type_node = try transQualType(c, scope, field_qt, loc); + init_expr = try removeCVQualifiers(c, dst_type_node, init_expr); + } + } try field_inits.append(.{ .name = raw_name, - .value = try transExpr(c, scope, elem_expr, .used), + .value = init_expr, }); } if (ty_node.castTag(.identifier)) |ident_node| { @@ -3459,6 +3499,10 @@ fn transCallExpr(c: *Context, scope: *Scope, stmt: *const clang.CallExpr, result const param_qt = fn_proto.getParamType(@intCast(c_uint, i)); if (isBoolRes(arg) and cIsNativeInt(param_qt)) { arg = try Tag.bool_to_int.create(c.arena, arg); + } else if (arg.tag() == .string_literal and qualTypeIsCharStar(param_qt)) { + const loc = @ptrCast(*const clang.Stmt, stmt).getBeginLoc(); + const dst_type_node = try transQualType(c, scope, param_qt, loc); + arg = try removeCVQualifiers(c, dst_type_node, arg); } } }, @@ -3835,6 +3879,12 @@ fn transCreateCompoundAssign( return block_scope.complete(c); } +// Casting away const or volatile requires us to use @intToPtr +fn removeCVQualifiers(c: *Context, dst_type_node: Node, expr: Node) Error!Node { + const ptr_to_int = try Tag.ptr_to_int.create(c.arena, expr); + return Tag.int_to_ptr.create(c.arena, .{ .lhs = dst_type_node, .rhs = ptr_to_int }); +} + fn transCPtrCast( c: *Context, scope: *Scope, @@ -3854,10 +3904,7 @@ fn transCPtrCast( (src_child_type.isVolatileQualified() and !child_type.isVolatileQualified()))) { - // Casting away const or volatile requires us to use @intToPtr - const ptr_to_int = try Tag.ptr_to_int.create(c.arena, expr); - const int_to_ptr = try Tag.int_to_ptr.create(c.arena, .{ .lhs = dst_type_node, .rhs = ptr_to_int }); - return int_to_ptr; + return removeCVQualifiers(c, dst_type_node, expr); } else { // Implicit downcasting from higher to lower alignment values is forbidden, // use @alignCast to side-step this problem @@ -4217,6 +4264,26 @@ fn typeIsOpaque(c: *Context, ty: *const clang.Type, loc: clang.SourceLocation) b } } +/// plain `char *` (not const; not explicitly signed or unsigned) +fn qualTypeIsCharStar(qt: clang.QualType) bool { + if (qualTypeIsPtr(qt)) { + const child_qt = qualTypeCanon(qt).getPointeeType(); + return cIsUnqualifiedChar(child_qt) and !child_qt.isConstQualified(); + } + return false; +} + +/// C `char` without explicit signed or unsigned qualifier +fn cIsUnqualifiedChar(qt: clang.QualType) bool { + const c_type = qualTypeCanon(qt); + if (c_type.getTypeClass() != .Builtin) return false; + const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type); + return switch (builtin_ty.getKind()) { + .Char_S, .Char_U => true, + else => false, + }; +} + fn cIsInteger(qt: clang.QualType) bool { return cIsSignedInteger(qt) or cIsUnsignedInteger(qt); } diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index fa6b749589..e1da1c7d05 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -62,6 +62,8 @@ pub const Node = extern union { var_decl, /// const name = struct { init } static_local_var, + /// var name = init.* + mut_str, func, warning, @"struct", @@ -361,7 +363,7 @@ pub const Node = extern union { .array_type, .null_sentinel_array_type => Payload.Array, .arg_redecl, .alias, .fail_decl => Payload.ArgRedecl, .log2_int_type => Payload.Log2IntType, - .var_simple, .pub_var_simple, .static_local_var => Payload.SimpleVarDecl, + .var_simple, .pub_var_simple, .static_local_var, .mut_str => Payload.SimpleVarDecl, .enum_constant => Payload.EnumConstant, .array_filler => Payload.ArrayFiller, .pub_inline_fn => Payload.PubInlineFn, @@ -558,9 +560,10 @@ pub const Payload = struct { pub const Record = struct { base: Payload, data: struct { - is_packed: bool, + layout: enum { @"packed", @"extern", none }, fields: []Field, functions: []Node, + variables: []Node, }, pub const Field = struct { @@ -1229,6 +1232,7 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { }, }); _ = try c.addToken(.r_brace, "}"); + _ = try c.addToken(.semicolon, ";"); return c.addNode(.{ .tag = .simple_var_decl, @@ -1239,6 +1243,29 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { }, }); }, + .mut_str => { + const payload = node.castTag(.mut_str).?.data; + + const var_tok = try c.addToken(.keyword_var, "var"); + _ = try c.addIdentifier(payload.name); + _ = try c.addToken(.equal, "="); + + const deref = try c.addNode(.{ + .tag = .deref, + .data = .{ + .lhs = try renderNodeGrouped(c, payload.init), + .rhs = undefined, + }, + .main_token = try c.addToken(.period_asterisk, ".*"), + }); + _ = try c.addToken(.semicolon, ";"); + + return c.addNode(.{ + .tag = .simple_var_decl, + .main_token = var_tok, + .data = .{ .lhs = 0, .rhs = deref }, + }); + }, .var_decl => return renderVar(c, node), .arg_redecl, .alias => { const payload = @fieldParentPtr(Payload.ArgRedecl, "base", node.ptr_otherwise).data; @@ -1952,9 +1979,9 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { fn renderRecord(c: *Context, node: Node) !NodeIndex { const payload = @fieldParentPtr(Payload.Record, "base", node.ptr_otherwise).data; - if (payload.is_packed) + if (payload.layout == .@"packed") _ = try c.addToken(.keyword_packed, "packed") - else + else if (payload.layout == .@"extern") _ = try c.addToken(.keyword_extern, "extern"); const kind_tok = if (node.tag() == .@"struct") try c.addToken(.keyword_struct, "struct") @@ -1963,8 +1990,9 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex { _ = try c.addToken(.l_brace, "{"); + const num_vars = payload.variables.len; const num_funcs = payload.functions.len; - const total_members = payload.fields.len + num_funcs; + const total_members = payload.fields.len + num_vars + num_funcs; const members = try c.gpa.alloc(NodeIndex, std.math.max(total_members, 2)); defer c.gpa.free(members); members[0] = 0; @@ -2006,8 +2034,11 @@ fn renderRecord(c: *Context, node: Node) !NodeIndex { }); _ = try c.addToken(.comma, ","); } + for (payload.variables) |variable, i| { + members[payload.fields.len + i] = try renderNode(c, variable); + } for (payload.functions) |function, i| { - members[payload.fields.len + i] = try renderNode(c, function); + members[payload.fields.len + num_vars + i] = try renderNode(c, function); } _ = try c.addToken(.r_brace, "}"); @@ -2140,7 +2171,7 @@ fn renderNullSentinelArrayType(c: *Context, len: usize, elem_type: Node) !NodeIn fn addSemicolonIfNeeded(c: *Context, node: Node) !void { switch (node.tag()) { .warning => unreachable, - .var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch" => {}, + .var_decl, .var_simple, .arg_redecl, .alias, .block, .empty_block, .block_single, .@"switch", .static_local_var, .mut_str => {}, .while_true => { const payload = node.castTag(.while_true).?.data; return addSemicolonIfNotBlock(c, payload); @@ -2235,6 +2266,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .offset_of, .shuffle, .static_local_var, + .mut_str, => { // no grouping needed return renderNode(c, node); diff --git a/src/type.zig b/src/type.zig index 28b87a8afe..467e9c931b 100644 --- a/src/type.zig +++ b/src/type.zig @@ -133,6 +133,7 @@ pub const Type = extern union { .@"union", .union_tagged, + .type_info, => return .Union, .var_args_param => unreachable, // can be any type @@ -248,6 +249,30 @@ pub const Type = extern union { }; } + pub fn ptrIsMutable(ty: Type) bool { + return switch (ty.tag()) { + .single_const_pointer_to_comptime_int, + .const_slice_u8, + .single_const_pointer, + .many_const_pointer, + .manyptr_const_u8, + .c_const_pointer, + .const_slice, + => false, + + .single_mut_pointer, + .many_mut_pointer, + .manyptr_u8, + .c_mut_pointer, + .mut_slice, + => true, + + .pointer => ty.castTag(.pointer).?.data.mutable, + + else => unreachable, + }; + } + pub fn ptrInfo(self: Type) Payload.Pointer { switch (self.tag()) { .single_const_pointer_to_comptime_int => return .{ .data = .{ @@ -534,15 +559,24 @@ pub const Type = extern union { return a_data.error_set.eql(b_data.error_set) and a_data.payload.eql(b_data.payload); }, .ErrorSet => { - const a_is_anyerror = a.tag() == .anyerror; - const b_is_anyerror = b.tag() == .anyerror; + if (a.tag() == .anyerror and b.tag() == .anyerror) { + return true; + } - if (a_is_anyerror and b_is_anyerror) return true; - if (a_is_anyerror or b_is_anyerror) return false; + if (a.tag() == .error_set and b.tag() == .error_set) { + return a.castTag(.error_set).?.data.owner_decl == b.castTag(.error_set).?.data.owner_decl; + } - std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ - a.tag(), b.tag(), - }); + if (a.tag() == .error_set_inferred and b.tag() == .error_set_inferred) { + return a.castTag(.error_set_inferred).?.data.func == b.castTag(.error_set_inferred).?.data.func; + } + + if (a.tag() == .error_set_single and b.tag() == .error_set_single) { + const a_data = a.castTag(.error_set_single).?.data; + const b_data = b.castTag(.error_set_single).?.data; + return std.mem.eql(u8, a_data, b_data); + } + return false; }, .Opaque, .Float, @@ -708,6 +742,7 @@ pub const Type = extern union { .call_options, .export_options, .extern_options, + .type_info, .@"anyframe", .generic_poison, => unreachable, @@ -919,6 +954,7 @@ pub const Type = extern union { .call_options => return writer.writeAll("std.builtin.CallOptions"), .export_options => return writer.writeAll("std.builtin.ExportOptions"), .extern_options => return writer.writeAll("std.builtin.ExternOptions"), + .type_info => return writer.writeAll("std.builtin.TypeInfo"), .function => { const payload = ty.castTag(.function).?.data; try writer.writeAll("fn("); @@ -1169,6 +1205,7 @@ pub const Type = extern union { .comptime_int, .comptime_float, .enum_literal, + .type_info, => true, .var_args_param => unreachable, @@ -1260,6 +1297,7 @@ pub const Type = extern union { .call_options => return Value.initTag(.call_options_type), .export_options => return Value.initTag(.export_options_type), .extern_options => return Value.initTag(.extern_options_type), + .type_info => return Value.initTag(.type_info_type), .inferred_alloc_const => unreachable, .inferred_alloc_mut => unreachable, else => return Value.Tag.ty.create(allocator, self), @@ -1400,6 +1438,7 @@ pub const Type = extern union { .empty_struct, .empty_struct_literal, .@"opaque", + .type_info, => false, .inferred_alloc_const => unreachable, @@ -1627,6 +1666,7 @@ pub const Type = extern union { .inferred_alloc_mut, .@"opaque", .var_args_param, + .type_info, => unreachable, .generic_poison => unreachable, @@ -1658,6 +1698,7 @@ pub const Type = extern union { .@"opaque" => unreachable, .var_args_param => unreachable, .generic_poison => unreachable, + .type_info => unreachable, .@"struct" => { const s = self.castTag(.@"struct").?.data; @@ -1969,6 +2010,7 @@ pub const Type = extern union { .call_options, .export_options, .extern_options, + .type_info, => @panic("TODO at some point we gotta resolve builtin types"), }; } @@ -2682,6 +2724,7 @@ pub const Type = extern union { .call_options, .export_options, .extern_options, + .type_info, .@"anyframe", .anyframe_T, .many_const_pointer, @@ -2769,6 +2812,7 @@ pub const Type = extern union { return switch (self.tag()) { .@"struct" => &self.castTag(.@"struct").?.data.namespace, .enum_full => &self.castTag(.enum_full).?.data.namespace, + .enum_nonexhaustive => &self.castTag(.enum_nonexhaustive).?.data.namespace, .empty_struct => self.castTag(.empty_struct).?.data, .@"opaque" => &self.castTag(.@"opaque").?.data, .@"union" => &self.castTag(.@"union").?.data.namespace, @@ -3013,6 +3057,7 @@ pub const Type = extern union { .call_options, .export_options, .extern_options, + .type_info, => @panic("TODO resolve std.builtin types"), else => unreachable, } @@ -3049,6 +3094,7 @@ pub const Type = extern union { .call_options, .export_options, .extern_options, + .type_info, => @panic("TODO resolve std.builtin types"), else => unreachable, } @@ -3158,6 +3204,7 @@ pub const Type = extern union { call_options, export_options, extern_options, + type_info, manyptr_u8, manyptr_const_u8, fn_noreturn_no_args, @@ -3280,6 +3327,7 @@ pub const Type = extern union { .call_options, .export_options, .extern_options, + .type_info, .@"anyframe", => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), diff --git a/src/value.zig b/src/value.zig index 562d7171e8..5ac9f142c4 100644 --- a/src/value.zig +++ b/src/value.zig @@ -68,6 +68,7 @@ pub const Value = extern union { call_options_type, export_options_type, extern_options_type, + type_info_type, manyptr_u8_type, manyptr_const_u8_type, fn_noreturn_no_args_type, @@ -132,12 +133,21 @@ pub const Value = extern union { /// When the type is error union: /// * If the tag is `.@"error"`, the error union is an error. /// * If the tag is `.eu_payload`, the error union is a payload. - /// * A nested error such as `((anyerror!T1)!T2)` in which the the outer error union + /// * A nested error such as `anyerror!(anyerror!T)` in which the the outer error union /// is non-error, but the inner error union is an error, is represented as /// a tag of `.eu_payload`, with a sub-tag of `.@"error"`. eu_payload, /// A pointer to the payload of an error union, based on a pointer to an error union. eu_payload_ptr, + /// When the type is optional: + /// * If the tag is `.null_value`, the optional is null. + /// * If the tag is `.opt_payload`, the optional is a payload. + /// * A nested optional such as `??T` in which the the outer optional + /// is non-null, but the inner optional is null, is represented as + /// a tag of `.opt_payload`, with a sub-tag of `.null_value`. + opt_payload, + /// A pointer to the payload of an optional, based on a pointer to an optional. + opt_payload_ptr, /// An instance of a struct. @"struct", /// An instance of a union. @@ -221,6 +231,7 @@ pub const Value = extern union { .call_options_type, .export_options_type, .extern_options_type, + .type_info_type, .generic_poison, => @compileError("Value Tag " ++ @tagName(t) ++ " has no payload"), @@ -236,6 +247,8 @@ pub const Value = extern union { .repeated, .eu_payload, .eu_payload_ptr, + .opt_payload, + .opt_payload_ptr, => Payload.SubValue, .bytes, @@ -402,6 +415,7 @@ pub const Value = extern union { .call_options_type, .export_options_type, .extern_options_type, + .type_info_type, .generic_poison, => unreachable, @@ -456,7 +470,12 @@ pub const Value = extern union { return Value{ .ptr_otherwise = &new_payload.base }; }, .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), - .repeated, .eu_payload, .eu_payload_ptr => { + .repeated, + .eu_payload, + .eu_payload_ptr, + .opt_payload, + .opt_payload_ptr, + => { const payload = self.cast(Payload.SubValue).?; const new_payload = try allocator.create(Payload.SubValue); new_payload.* = .{ @@ -585,6 +604,7 @@ pub const Value = extern union { .call_options_type => return out_stream.writeAll("std.builtin.CallOptions"), .export_options_type => return out_stream.writeAll("std.builtin.ExportOptions"), .extern_options_type => return out_stream.writeAll("std.builtin.ExternOptions"), + .type_info_type => return out_stream.writeAll("std.builtin.TypeInfo"), .abi_align_default => return out_stream.writeAll("(default ABI alignment)"), .empty_struct_value => return out_stream.writeAll("struct {}{}"), @@ -652,12 +672,20 @@ pub const Value = extern union { try out_stream.writeAll("(eu_payload) "); val = val.castTag(.eu_payload).?.data; }, + .opt_payload => { + try out_stream.writeAll("(opt_payload) "); + val = val.castTag(.opt_payload).?.data; + }, .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"), .inferred_alloc_comptime => return out_stream.writeAll("(inferred comptime allocation value)"), .eu_payload_ptr => { try out_stream.writeAll("(eu_payload_ptr)"); val = val.castTag(.eu_payload_ptr).?.data; }, + .opt_payload_ptr => { + try out_stream.writeAll("(opt_payload_ptr)"); + val = val.castTag(.opt_payload_ptr).?.data; + }, }; } @@ -743,6 +771,7 @@ pub const Value = extern union { .call_options_type => Type.initTag(.call_options), .export_options_type => Type.initTag(.export_options), .extern_options_type => Type.initTag(.extern_options), + .type_info_type => Type.initTag(.type_info), .int_type => { const payload = self.castTag(.int_type).?.data; @@ -771,6 +800,38 @@ pub const Value = extern union { } } + pub fn enumToInt(val: Value, ty: Type, buffer: *Payload.U64) Value { + if (val.castTag(.enum_field_index)) |enum_field_payload| { + const field_index = enum_field_payload.data; + switch (ty.tag()) { + .enum_full, .enum_nonexhaustive => { + const enum_full = ty.cast(Type.Payload.EnumFull).?.data; + if (enum_full.values.count() != 0) { + return enum_full.values.keys()[field_index]; + } else { + // Field index and integer values are the same. + buffer.* = .{ + .base = .{ .tag = .int_u64 }, + .data = field_index, + }; + return Value.initPayload(&buffer.base); + } + }, + .enum_simple => { + // Field index and integer values are the same. + buffer.* = .{ + .base = .{ .tag = .int_u64 }, + .data = field_index, + }; + return Value.initPayload(&buffer.base); + }, + else => unreachable, + } + } + // Assume it is already an integer and return it directly. + return val; + } + /// Asserts the value is an integer. pub fn toBigInt(self: Value, space: *BigIntSpace) BigIntConst { switch (self.tag()) { @@ -1127,7 +1188,10 @@ pub const Value = extern union { } pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void { - switch (ty.zigTypeTag()) { + const zig_ty_tag = ty.zigTypeTag(); + std.hash.autoHash(hasher, zig_ty_tag); + + switch (zig_ty_tag) { .BoundFn => unreachable, // TODO remove this from the language .Void, @@ -1152,7 +1216,10 @@ pub const Value = extern union { } }, .Float, .ComptimeFloat => { - @panic("TODO implement hashing float values"); + // TODO double check the lang spec. should we to bitwise hashing here, + // or a hash that normalizes the float value? + const float = val.toFloat(f128); + std.hash.autoHash(hasher, @bitCast(u128, float)); }, .Pointer => { @panic("TODO implement hashing pointer values"); @@ -1164,7 +1231,15 @@ pub const Value = extern union { @panic("TODO implement hashing struct values"); }, .Optional => { - @panic("TODO implement hashing optional values"); + if (val.castTag(.opt_payload)) |payload| { + std.hash.autoHash(hasher, true); // non-null + const sub_val = payload.data; + var buffer: Type.Payload.ElemType = undefined; + const sub_ty = ty.optionalChild(&buffer); + sub_val.hash(sub_ty, hasher); + } else { + std.hash.autoHash(hasher, false); // non-null + } }, .ErrorUnion => { @panic("TODO implement hashing error union values"); @@ -1173,7 +1248,16 @@ pub const Value = extern union { @panic("TODO implement hashing error set values"); }, .Enum => { - @panic("TODO implement hashing enum values"); + var enum_space: Payload.U64 = undefined; + const int_val = val.enumToInt(ty, &enum_space); + + var space: BigIntSpace = undefined; + const big = int_val.toBigInt(&space); + + std.hash.autoHash(hasher, big.positive); + for (big.limbs) |limb| { + std.hash.autoHash(hasher, limb); + } }, .Union => { @panic("TODO implement hashing union values"); @@ -1252,6 +1336,11 @@ pub const Value = extern union { const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null; break :blk err_union_val.castTag(.eu_payload).?.data; }, + .opt_payload_ptr => blk: { + const opt_ptr = self.castTag(.opt_payload_ptr).?.data; + const opt_val = (try opt_ptr.pointerDeref(allocator)) orelse return null; + break :blk opt_val.castTag(.opt_payload).?.data; + }, .zero, .one, @@ -1349,13 +1438,14 @@ pub const Value = extern union { /// Valid for all types. Asserts the value is not undefined and not unreachable. pub fn isNull(self: Value) bool { return switch (self.tag()) { + .null_value => true, + .opt_payload => false, + .undef => unreachable, .unreachable_value => unreachable, .inferred_alloc => unreachable, .inferred_alloc_comptime => unreachable, - .null_value => true, - - else => false, + else => unreachable, }; } @@ -1385,6 +1475,10 @@ pub const Value = extern union { return switch (val.tag()) { .eu_payload => true, else => false, + + .undef => unreachable, + .inferred_alloc => unreachable, + .inferred_alloc_comptime => unreachable, }; } @@ -1514,6 +1608,31 @@ pub const Value = extern union { return Tag.int_u64.create(arena, truncated); } + pub fn shr(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; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const shift = rhs.toUnsignedInt(); + const limbs = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len - (shift / (@sizeOf(std.math.big.Limb) * 8)), + ); + var result_bigint = BigIntMutable{ + .limbs = limbs, + .positive = undefined, + .len = undefined, + }; + result_bigint.shiftRight(lhs_bigint, shift); + 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( lhs: Value, rhs: Value, |
