Commit cb3198af2a
Changed files (4)
test
stage2
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);