diff --git a/src/astgen.zig b/src/astgen.zig index bef16445d8..622a18c443 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -534,7 +534,7 @@ fn varDecl( // Depending on the type of AST the initialization expression is, we may need an lvalue // or an rvalue as a result location. If it is an rvalue, we can use the instruction as // the variable, no memory location needed. - const result_loc = if (nodeMayNeedMemoryLocation(init_node)) r: { + const result_loc = if (nodeMayNeedMemoryLocation(init_node, scope)) r: { if (node.getTypeNode()) |type_node| { const type_inst = try typeExpr(mod, scope, type_node); const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst); @@ -1831,7 +1831,7 @@ fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerE const tree = scope.tree(); const src = tree.token_locs[cfe.ltoken].start; if (cfe.getRHS()) |rhs_node| { - if (nodeMayNeedMemoryLocation(rhs_node)) { + if (nodeMayNeedMemoryLocation(rhs_node, scope)) { const ret_ptr = try addZIRNoOp(mod, scope, src, .ret_ptr); const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node); return addZIRUnOp(mod, scope, src, .@"return", operand); @@ -2248,6 +2248,23 @@ fn import(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!* return addZIRUnOp(mod, scope, src, .import, target); } +fn typeOf(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + const tree = scope.tree(); + const arena = scope.arena(); + const src = tree.token_locs[call.builtin_token].start; + const params = call.params(); + if (params.len < 1) { + return mod.failTok(scope, call.builtin_token, "expected at least 1 argument, found 0", .{}); + } + if (params.len == 1) { + return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .typeof, try expr(mod, scope, .none, params[0]))); + } + var items = try arena.alloc(*zir.Inst, params.len); + for (params) |param, param_i| + items[param_i] = try expr(mod, scope, .none, param); + return rlWrap(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.TypeOfPeer, .{ .items = items }, .{})); +} + fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { const tree = scope.tree(); const builtin_name = tree.tokenSlice(call.builtin_token); @@ -2267,6 +2284,8 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built return simpleCast(mod, scope, rl, call, .intcast); } else if (mem.eql(u8, builtin_name, "@bitCast")) { return bitCast(mod, scope, rl, call); + } else if (mem.eql(u8, builtin_name, "@TypeOf")) { + return typeOf(mod, scope, rl, call); } else if (mem.eql(u8, builtin_name, "@breakpoint")) { const src = tree.token_locs[call.builtin_token].start; return rlWrap(mod, scope, rl, try addZIRNoOp(mod, scope, src, .breakpoint)); @@ -2344,7 +2363,7 @@ fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { return null; } -fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool { +fn nodeMayNeedMemoryLocation(start_node: *ast.Node, scope: *Scope) bool { var node = start_node; while (true) { switch (node.tag) { @@ -2468,10 +2487,120 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool { .For, .Switch, .Call, - .BuiltinCall, // TODO some of these can return false .LabeledBlock, => return true, + .BuiltinCall => { + @setEvalBranchQuota(5000); + const builtin_needs_mem_loc = std.ComptimeStringMap(bool, .{ + .{ "@addWithOverflow", false }, + .{ "@alignCast", false }, + .{ "@alignOf", false }, + .{ "@as", true }, + .{ "@asyncCall", false }, + .{ "@atomicLoad", false }, + .{ "@atomicRmw", false }, + .{ "@atomicStore", false }, + .{ "@bitCast", true }, + .{ "@bitOffsetOf", false }, + .{ "@boolToInt", false }, + .{ "@bitSizeOf", false }, + .{ "@breakpoint", false }, + .{ "@mulAdd", false }, + .{ "@byteSwap", false }, + .{ "@bitReverse", false }, + .{ "@byteOffsetOf", false }, + .{ "@call", true }, + .{ "@cDefine", false }, + .{ "@cImport", false }, + .{ "@cInclude", false }, + .{ "@clz", false }, + .{ "@cmpxchgStrong", false }, + .{ "@cmpxchgWeak", false }, + .{ "@compileError", false }, + .{ "@compileLog", false }, + .{ "@ctz", false }, + .{ "@cUndef", false }, + .{ "@divExact", false }, + .{ "@divFloor", false }, + .{ "@divTrunc", false }, + .{ "@embedFile", false }, + .{ "@enumToInt", false }, + .{ "@errorName", false }, + .{ "@errorReturnTrace", false }, + .{ "@errorToInt", false }, + .{ "@errSetCast", false }, + .{ "@export", false }, + .{ "@fence", false }, + .{ "@field", true }, + .{ "@fieldParentPtr", false }, + .{ "@floatCast", false }, + .{ "@floatToInt", false }, + .{ "@frame", false }, + .{ "@Frame", false }, + .{ "@frameAddress", false }, + .{ "@frameSize", false }, + .{ "@hasDecl", false }, + .{ "@hasField", false }, + .{ "@import", false }, + .{ "@intCast", false }, + .{ "@intToEnum", false }, + .{ "@intToError", false }, + .{ "@intToFloat", false }, + .{ "@intToPtr", false }, + .{ "@memcpy", false }, + .{ "@memset", false }, + .{ "@wasmMemorySize", false }, + .{ "@wasmMemoryGrow", false }, + .{ "@mod", false }, + .{ "@mulWithOverflow", false }, + .{ "@panic", false }, + .{ "@popCount", false }, + .{ "@ptrCast", false }, + .{ "@ptrToInt", false }, + .{ "@rem", false }, + .{ "@returnAddress", false }, + .{ "@setAlignStack", false }, + .{ "@setCold", false }, + .{ "@setEvalBranchQuota", false }, + .{ "@setFloatMode", false }, + .{ "@setRuntimeSafety", false }, + .{ "@shlExact", false }, + .{ "@shlWithOverflow", false }, + .{ "@shrExact", false }, + .{ "@shuffle", false }, + .{ "@sizeOf", false }, + .{ "@splat", true }, + .{ "@reduce", false }, + .{ "@src", true }, + .{ "@sqrt", false }, + .{ "@sin", false }, + .{ "@cos", false }, + .{ "@exp", false }, + .{ "@exp2", false }, + .{ "@log", false }, + .{ "@log2", false }, + .{ "@log10", false }, + .{ "@fabs", false }, + .{ "@floor", false }, + .{ "@ceil", false }, + .{ "@trunc", false }, + .{ "@round", false }, + .{ "@subWithOverflow", false }, + .{ "@tagName", false }, + .{ "@TagType", false }, + .{ "@This", false }, + .{ "@truncate", false }, + .{ "@Type", false }, + .{ "@typeInfo", false }, + .{ "@typeName", false }, + .{ "@TypeOf", false }, + .{ "@unionInit", true }, + }); + const name = scope.tree().tokenSlice(node.castTag(.BuiltinCall).?.builtin_token); + return builtin_needs_mem_loc.get(name).?; + }, + // Depending on AST properties, they may need memory locations. .If => return node.castTag(.If).?.@"else" != null, } diff --git a/src/zir.zig b/src/zir.zig index 6a59ac4994..d88978fa2f 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -251,6 +251,8 @@ pub const Inst = struct { subwrap, /// Returns the type of a value. typeof, + /// Is the builtin @TypeOf which returns the type after peertype resolution of one or more params + typeof_peer, /// Asserts control-flow will not reach this instruction. Not safety checked - the compiler /// will assume the correctness of this instruction. unreach_nocheck, @@ -403,6 +405,7 @@ pub const Inst = struct { .error_set => ErrorSet, .slice => Slice, .switchbr => SwitchBr, + .typeof_peer => TypeOfPeer, }; } @@ -510,6 +513,7 @@ pub const Inst = struct { .slice_start, .import, .switch_range, + .typeof_peer, => false, .@"break", @@ -1032,6 +1036,14 @@ pub const Inst = struct { body: Module.Body, }; }; + pub const TypeOfPeer = struct { + pub const base_tag = .typeof_peer; + base: Inst, + positionals: struct { + items: []*Inst, + }, + kw_args: struct {}, + }; }; pub const ErrorMsg = struct { diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 602da97f81..6f5d9511d5 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -118,6 +118,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .iserr => return analyzeInstIsErr(mod, scope, old_inst.castTag(.iserr).?), .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?), .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?), + .typeof_peer => return analyzeInstTypeOfPeer(mod, scope, old_inst.castTag(.typeof_peer).?), .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?), .unwrap_optional_safe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_safe).?, true), .unwrap_optional_unsafe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_unsafe).?, false), @@ -1663,6 +1664,11 @@ fn analyzeInstCmp( // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for // numeric types. return mod.cmpNumeric(scope, inst.base.src, lhs, rhs, op); + } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) { + if (!is_equality_cmp) { + return mod.fail(scope, inst.base.src, "{} operator not allowed for types", .{@tagName(op)}); + } + return mod.constBool(scope, inst.base.src, lhs.value().?.eql(rhs.value().?) == (op == .eq)); } return mod.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{}); } @@ -1672,6 +1678,16 @@ fn analyzeInstTypeOf(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErr return mod.constType(scope, inst.base.src, operand.ty); } +fn analyzeInstTypeOfPeer(mod: *Module, scope: *Scope, inst: *zir.Inst.TypeOfPeer) InnerError!*Inst { + var insts_to_res = try mod.gpa.alloc(*ir.Inst, inst.positionals.items.len); + defer mod.gpa.free(insts_to_res); + for (inst.positionals.items) |item, i| { + insts_to_res[i] = try resolveInst(mod, scope, item); + } + const pt_res = try mod.resolvePeerTypes(scope, insts_to_res); + return mod.constType(scope, inst.base.src, pt_res); +} + fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const uncasted_operand = try resolveInst(mod, scope, inst.positionals.operand); const bool_type = Type.initTag(.bool); diff --git a/test/stage2/test.zig b/test/stage2/test.zig index a2daa933a1..3da497fb9b 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -370,6 +370,64 @@ pub fn addCases(ctx: *TestContext) !void { "", ); } + { + var case = ctx.exe("@TypeOf", linux_x64); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ var x: usize = 0; + \\ const z = @TypeOf(x, @as(u128, 5)); + \\ assert(z == u128); + \\ + \\ exit(); + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ const z = @TypeOf(true); + \\ assert(z == bool); + \\ + \\ exit(); + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + case.addError( + \\export fn _start() noreturn { + \\ const z = @TypeOf(true, 1); + \\ unreachable; + \\} + , &[_][]const u8{":2:29: error: incompatible types: 'bool' and 'comptime_int'"}); + } { var case = ctx.exe("assert function", linux_x64);