authorgravatar for 58830309+g-w1@users.noreply.github.comg-w1 <58830309+g-w1@users.noreply.github.com> 2020-12-22 18:26:36-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-12-23 01:26:36+02:00
logcb3198af2af2501c1caf04228736d0b94a371f97
tree012c0f5507068c5fe1e9ec9e8a8f80da872791b9
parentea18f894f524fdc82dc09ea9c6abf8feb93b04e8
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

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>

4 files changed, 219 insertions(+), 4 deletions(-)

src/astgen.zig+133-4
...@@ -534,7 +534,7 @@ fn varDecl(...@@ -534,7 +534,7 @@ fn varDecl(
534 // Depending on the type of AST the initialization expression is, we may need an lvalue534 // Depending on the type of AST the initialization expression is, we may need an lvalue
535 // or an rvalue as a result location. If it is an rvalue, we can use the instruction as535 // or an rvalue as a result location. If it is an rvalue, we can use the instruction as
536 // the variable, no memory location needed.536 // the variable, no memory location needed.
537 const result_loc = if (nodeMayNeedMemoryLocation(init_node)) r: {537 const result_loc = if (nodeMayNeedMemoryLocation(init_node, scope)) r: {
538 if (node.getTypeNode()) |type_node| {538 if (node.getTypeNode()) |type_node| {
539 const type_inst = try typeExpr(mod, scope, type_node);539 const type_inst = try typeExpr(mod, scope, type_node);
540 const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst);540 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...@@ -1831,7 +1831,7 @@ fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerE
1831 const tree = scope.tree();1831 const tree = scope.tree();
1832 const src = tree.token_locs[cfe.ltoken].start;1832 const src = tree.token_locs[cfe.ltoken].start;
1833 if (cfe.getRHS()) |rhs_node| {1833 if (cfe.getRHS()) |rhs_node| {
1834 if (nodeMayNeedMemoryLocation(rhs_node)) {1834 if (nodeMayNeedMemoryLocation(rhs_node, scope)) {
1835 const ret_ptr = try addZIRNoOp(mod, scope, src, .ret_ptr);1835 const ret_ptr = try addZIRNoOp(mod, scope, src, .ret_ptr);
1836 const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node);1836 const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node);
1837 return addZIRUnOp(mod, scope, src, .@"return", operand);1837 return addZIRUnOp(mod, scope, src, .@"return", operand);
...@@ -2248,6 +2248,23 @@ fn import(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*...@@ -2248,6 +2248,23 @@ fn import(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*
2248 return addZIRUnOp(mod, scope, src, .import, target);2248 return addZIRUnOp(mod, scope, src, .import, target);
2249}2249}
22502250
2251fn typeOf(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst {
2252 const tree = scope.tree();
2253 const arena = scope.arena();
2254 const src = tree.token_locs[call.builtin_token].start;
2255 const params = call.params();
2256 if (params.len < 1) {
2257 return mod.failTok(scope, call.builtin_token, "expected at least 1 argument, found 0", .{});
2258 }
2259 if (params.len == 1) {
2260 return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .typeof, try expr(mod, scope, .none, params[0])));
2261 }
2262 var items = try arena.alloc(*zir.Inst, params.len);
2263 for (params) |param, param_i|
2264 items[param_i] = try expr(mod, scope, .none, param);
2265 return rlWrap(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.TypeOfPeer, .{ .items = items }, .{}));
2266}
2267
2251fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst {2268fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst {
2252 const tree = scope.tree();2269 const tree = scope.tree();
2253 const builtin_name = tree.tokenSlice(call.builtin_token);2270 const builtin_name = tree.tokenSlice(call.builtin_token);
...@@ -2267,6 +2284,8 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built...@@ -2267,6 +2284,8 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built
2267 return simpleCast(mod, scope, rl, call, .intcast);2284 return simpleCast(mod, scope, rl, call, .intcast);
2268 } else if (mem.eql(u8, builtin_name, "@bitCast")) {2285 } else if (mem.eql(u8, builtin_name, "@bitCast")) {
2269 return bitCast(mod, scope, rl, call);2286 return bitCast(mod, scope, rl, call);
2287 } else if (mem.eql(u8, builtin_name, "@TypeOf")) {
2288 return typeOf(mod, scope, rl, call);
2270 } else if (mem.eql(u8, builtin_name, "@breakpoint")) {2289 } else if (mem.eql(u8, builtin_name, "@breakpoint")) {
2271 const src = tree.token_locs[call.builtin_token].start;2290 const src = tree.token_locs[call.builtin_token].start;
2272 return rlWrap(mod, scope, rl, try addZIRNoOp(mod, scope, src, .breakpoint));2291 return rlWrap(mod, scope, rl, try addZIRNoOp(mod, scope, src, .breakpoint));
...@@ -2344,7 +2363,7 @@ fn getSimplePrimitiveValue(name: []const u8) ?TypedValue {...@@ -2344,7 +2363,7 @@ fn getSimplePrimitiveValue(name: []const u8) ?TypedValue {
2344 return null;2363 return null;
2345}2364}
23462365
2347fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {2366fn nodeMayNeedMemoryLocation(start_node: *ast.Node, scope: *Scope) bool {
2348 var node = start_node;2367 var node = start_node;
2349 while (true) {2368 while (true) {
2350 switch (node.tag) {2369 switch (node.tag) {
...@@ -2468,10 +2487,120 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {...@@ -2468,10 +2487,120 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {
2468 .For,2487 .For,
2469 .Switch,2488 .Switch,
2470 .Call,2489 .Call,
2471 .BuiltinCall, // TODO some of these can return false
2472 .LabeledBlock,2490 .LabeledBlock,
2473 => return true,2491 => return true,
24742492
2493 .BuiltinCall => {
2494 @setEvalBranchQuota(5000);
2495 const builtin_needs_mem_loc = std.ComptimeStringMap(bool, .{
2496 .{ "@addWithOverflow", false },
2497 .{ "@alignCast", false },
2498 .{ "@alignOf", false },
2499 .{ "@as", true },
2500 .{ "@asyncCall", false },
2501 .{ "@atomicLoad", false },
2502 .{ "@atomicRmw", false },
2503 .{ "@atomicStore", false },
2504 .{ "@bitCast", true },
2505 .{ "@bitOffsetOf", false },
2506 .{ "@boolToInt", false },
2507 .{ "@bitSizeOf", false },
2508 .{ "@breakpoint", false },
2509 .{ "@mulAdd", false },
2510 .{ "@byteSwap", false },
2511 .{ "@bitReverse", false },
2512 .{ "@byteOffsetOf", false },
2513 .{ "@call", true },
2514 .{ "@cDefine", false },
2515 .{ "@cImport", false },
2516 .{ "@cInclude", false },
2517 .{ "@clz", false },
2518 .{ "@cmpxchgStrong", false },
2519 .{ "@cmpxchgWeak", false },
2520 .{ "@compileError", false },
2521 .{ "@compileLog", false },
2522 .{ "@ctz", false },
2523 .{ "@cUndef", false },
2524 .{ "@divExact", false },
2525 .{ "@divFloor", false },
2526 .{ "@divTrunc", false },
2527 .{ "@embedFile", false },
2528 .{ "@enumToInt", false },
2529 .{ "@errorName", false },
2530 .{ "@errorReturnTrace", false },
2531 .{ "@errorToInt", false },
2532 .{ "@errSetCast", false },
2533 .{ "@export", false },
2534 .{ "@fence", false },
2535 .{ "@field", true },
2536 .{ "@fieldParentPtr", false },
2537 .{ "@floatCast", false },
2538 .{ "@floatToInt", false },
2539 .{ "@frame", false },
2540 .{ "@Frame", false },
2541 .{ "@frameAddress", false },
2542 .{ "@frameSize", false },
2543 .{ "@hasDecl", false },
2544 .{ "@hasField", false },
2545 .{ "@import", false },
2546 .{ "@intCast", false },
2547 .{ "@intToEnum", false },
2548 .{ "@intToError", false },
2549 .{ "@intToFloat", false },
2550 .{ "@intToPtr", false },
2551 .{ "@memcpy", false },
2552 .{ "@memset", false },
2553 .{ "@wasmMemorySize", false },
2554 .{ "@wasmMemoryGrow", false },
2555 .{ "@mod", false },
2556 .{ "@mulWithOverflow", false },
2557 .{ "@panic", false },
2558 .{ "@popCount", false },
2559 .{ "@ptrCast", false },
2560 .{ "@ptrToInt", false },
2561 .{ "@rem", false },
2562 .{ "@returnAddress", false },
2563 .{ "@setAlignStack", false },
2564 .{ "@setCold", false },
2565 .{ "@setEvalBranchQuota", false },
2566 .{ "@setFloatMode", false },
2567 .{ "@setRuntimeSafety", false },
2568 .{ "@shlExact", false },
2569 .{ "@shlWithOverflow", false },
2570 .{ "@shrExact", false },
2571 .{ "@shuffle", false },
2572 .{ "@sizeOf", false },
2573 .{ "@splat", true },
2574 .{ "@reduce", false },
2575 .{ "@src", true },
2576 .{ "@sqrt", false },
2577 .{ "@sin", false },
2578 .{ "@cos", false },
2579 .{ "@exp", false },
2580 .{ "@exp2", false },
2581 .{ "@log", false },
2582 .{ "@log2", false },
2583 .{ "@log10", false },
2584 .{ "@fabs", false },
2585 .{ "@floor", false },
2586 .{ "@ceil", false },
2587 .{ "@trunc", false },
2588 .{ "@round", false },
2589 .{ "@subWithOverflow", false },
2590 .{ "@tagName", false },
2591 .{ "@TagType", false },
2592 .{ "@This", false },
2593 .{ "@truncate", false },
2594 .{ "@Type", false },
2595 .{ "@typeInfo", false },
2596 .{ "@typeName", false },
2597 .{ "@TypeOf", false },
2598 .{ "@unionInit", true },
2599 });
2600 const name = scope.tree().tokenSlice(node.castTag(.BuiltinCall).?.builtin_token);
2601 return builtin_needs_mem_loc.get(name).?;
2602 },
2603
2475 // Depending on AST properties, they may need memory locations.2604 // Depending on AST properties, they may need memory locations.
2476 .If => return node.castTag(.If).?.@"else" != null,2605 .If => return node.castTag(.If).?.@"else" != null,
2477 }2606 }
src/zir.zig+12
...@@ -251,6 +251,8 @@ pub const Inst = struct {...@@ -251,6 +251,8 @@ pub const Inst = struct {
251 subwrap,251 subwrap,
252 /// Returns the type of a value.252 /// Returns the type of a value.
253 typeof,253 typeof,
254 /// Is the builtin @TypeOf which returns the type after peertype resolution of one or more params
255 typeof_peer,
254 /// Asserts control-flow will not reach this instruction. Not safety checked - the compiler256 /// Asserts control-flow will not reach this instruction. Not safety checked - the compiler
255 /// will assume the correctness of this instruction.257 /// will assume the correctness of this instruction.
256 unreach_nocheck,258 unreach_nocheck,
...@@ -403,6 +405,7 @@ pub const Inst = struct {...@@ -403,6 +405,7 @@ pub const Inst = struct {
403 .error_set => ErrorSet,405 .error_set => ErrorSet,
404 .slice => Slice,406 .slice => Slice,
405 .switchbr => SwitchBr,407 .switchbr => SwitchBr,
408 .typeof_peer => TypeOfPeer,
406 };409 };
407 }410 }
408411
...@@ -510,6 +513,7 @@ pub const Inst = struct {...@@ -510,6 +513,7 @@ pub const Inst = struct {
510 .slice_start,513 .slice_start,
511 .import,514 .import,
512 .switch_range,515 .switch_range,
516 .typeof_peer,
513 => false,517 => false,
514518
515 .@"break",519 .@"break",
...@@ -1032,6 +1036,14 @@ pub const Inst = struct {...@@ -1032,6 +1036,14 @@ pub const Inst = struct {
1032 body: Module.Body,1036 body: Module.Body,
1033 };1037 };
1034 };1038 };
1039 pub const TypeOfPeer = struct {
1040 pub const base_tag = .typeof_peer;
1041 base: Inst,
1042 positionals: struct {
1043 items: []*Inst,
1044 },
1045 kw_args: struct {},
1046 };
1035};1047};
10361048
1037pub const ErrorMsg = struct {1049pub const ErrorMsg = struct {
src/zir_sema.zig+16
...@@ -118,6 +118,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!...@@ -118,6 +118,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
118 .iserr => return analyzeInstIsErr(mod, scope, old_inst.castTag(.iserr).?),118 .iserr => return analyzeInstIsErr(mod, scope, old_inst.castTag(.iserr).?),
119 .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?),119 .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?),
120 .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?),120 .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?),
121 .typeof_peer => return analyzeInstTypeOfPeer(mod, scope, old_inst.castTag(.typeof_peer).?),
121 .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?),122 .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?),
122 .unwrap_optional_safe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_safe).?, true),123 .unwrap_optional_safe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_safe).?, true),
123 .unwrap_optional_unsafe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_unsafe).?, false),124 .unwrap_optional_unsafe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_unsafe).?, false),
...@@ -1663,6 +1664,11 @@ fn analyzeInstCmp(...@@ -1663,6 +1664,11 @@ fn analyzeInstCmp(
1663 // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for1664 // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
1664 // numeric types.1665 // numeric types.
1665 return mod.cmpNumeric(scope, inst.base.src, lhs, rhs, op);1666 return mod.cmpNumeric(scope, inst.base.src, lhs, rhs, op);
1667 } else if (lhs_ty_tag == .Type and rhs_ty_tag == .Type) {
1668 if (!is_equality_cmp) {
1669 return mod.fail(scope, inst.base.src, "{} operator not allowed for types", .{@tagName(op)});
1670 }
1671 return mod.constBool(scope, inst.base.src, lhs.value().?.eql(rhs.value().?) == (op == .eq));
1666 }1672 }
1667 return mod.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{});1673 return mod.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{});
1668}1674}
...@@ -1672,6 +1678,16 @@ fn analyzeInstTypeOf(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErr...@@ -1672,6 +1678,16 @@ fn analyzeInstTypeOf(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErr
1672 return mod.constType(scope, inst.base.src, operand.ty);1678 return mod.constType(scope, inst.base.src, operand.ty);
1673}1679}
16741680
1681fn analyzeInstTypeOfPeer(mod: *Module, scope: *Scope, inst: *zir.Inst.TypeOfPeer) InnerError!*Inst {
1682 var insts_to_res = try mod.gpa.alloc(*ir.Inst, inst.positionals.items.len);
1683 defer mod.gpa.free(insts_to_res);
1684 for (inst.positionals.items) |item, i| {
1685 insts_to_res[i] = try resolveInst(mod, scope, item);
1686 }
1687 const pt_res = try mod.resolvePeerTypes(scope, insts_to_res);
1688 return mod.constType(scope, inst.base.src, pt_res);
1689}
1690
1675fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {1691fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
1676 const uncasted_operand = try resolveInst(mod, scope, inst.positionals.operand);1692 const uncasted_operand = try resolveInst(mod, scope, inst.positionals.operand);
1677 const bool_type = Type.initTag(.bool);1693 const bool_type = Type.initTag(.bool);
test/stage2/test.zig+58
...@@ -370,6 +370,64 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -370,6 +370,64 @@ pub fn addCases(ctx: *TestContext) !void {
370 "",370 "",
371 );371 );
372 }372 }
373 {
374 var case = ctx.exe("@TypeOf", linux_x64);
375 case.addCompareOutput(
376 \\export fn _start() noreturn {
377 \\ var x: usize = 0;
378 \\ const z = @TypeOf(x, @as(u128, 5));
379 \\ assert(z == u128);
380 \\
381 \\ exit();
382 \\}
383 \\
384 \\pub fn assert(ok: bool) void {
385 \\ if (!ok) unreachable; // assertion failure
386 \\}
387 \\
388 \\fn exit() noreturn {
389 \\ asm volatile ("syscall"
390 \\ :
391 \\ : [number] "{rax}" (231),
392 \\ [arg1] "{rdi}" (0)
393 \\ : "rcx", "r11", "memory"
394 \\ );
395 \\ unreachable;
396 \\}
397 ,
398 "",
399 );
400 case.addCompareOutput(
401 \\export fn _start() noreturn {
402 \\ const z = @TypeOf(true);
403 \\ assert(z == bool);
404 \\
405 \\ exit();
406 \\}
407 \\
408 \\pub fn assert(ok: bool) void {
409 \\ if (!ok) unreachable; // assertion failure
410 \\}
411 \\
412 \\fn exit() noreturn {
413 \\ asm volatile ("syscall"
414 \\ :
415 \\ : [number] "{rax}" (231),
416 \\ [arg1] "{rdi}" (0)
417 \\ : "rcx", "r11", "memory"
418 \\ );
419 \\ unreachable;
420 \\}
421 ,
422 "",
423 );
424 case.addError(
425 \\export fn _start() noreturn {
426 \\ const z = @TypeOf(true, 1);
427 \\ unreachable;
428 \\}
429 , &[_][]const u8{":2:29: error: incompatible types: 'bool' and 'comptime_int'"});
430 }
373431
374 {432 {
375 var case = ctx.exe("assert function", linux_x64);433 var case = ctx.exe("assert function", linux_x64);