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:
g-w1 2020-12-22 18:26:36 -05:00 committed by GitHub
parent ea18f894f5
commit cb3198af2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 219 additions and 4 deletions

View File

@ -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,
}

View File

@ -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 {

View File

@ -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);

View File

@ -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);