Commit cb3198af2a

g-w1 <58830309+g-w1@users.noreply.github.com>
2020-12-23 00:26:36
stage2: @TypeOf (#7475)
* stage2: add @TypeOf * stage2: discriminate on what type of @builtinCall in nodeMayNeedMemoryLocation * merge upstream into my stash * add type equality to make easier to test and defer free the types * remove addDeclErr, I dont know why I added it, its from a different branch that im working on * add tests * update error message to match stage1 * use ComptimeStringMap and update which nodes don't need memory from vexu's suggestions * fix typo Co-authored-by: Veikka Tuominen <git@vexu.eu> * make @TypeOf(single_arg) go to .typeof zir inst and add test for that * unioninit, as, reduce change mayneedmemorylocation Co-authored-by: Veikka Tuominen <git@vexu.eu>
1 parent ea18f89
Changed files (4)
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,
         }
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 {
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);
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);