mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 21:08:36 +00:00
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>
This commit is contained in:
parent
ea18f894f5
commit
cb3198af2a
137
src/astgen.zig
137
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,
|
||||
}
|
||||
|
||||
12
src/zir.zig
12
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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user