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(
534534 // Depending on the type of AST the initialization expression is, we may need an lvalue
535535 // or an rvalue as a result location. If it is an rvalue, we can use the instruction as
536536 // 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: {
538538 if (node.getTypeNode()) |type_node| {
539539 const type_inst = try typeExpr(mod, scope, type_node);
540540 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
18311831 const tree = scope.tree();
18321832 const src = tree.token_locs[cfe.ltoken].start;
18331833 if (cfe.getRHS()) |rhs_node| {
1834 if (nodeMayNeedMemoryLocation(rhs_node)) {
1834 if (nodeMayNeedMemoryLocation(rhs_node, scope)) {
18351835 const ret_ptr = try addZIRNoOp(mod, scope, src, .ret_ptr);
18361836 const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node);
18371837 return addZIRUnOp(mod, scope, src, .@"return", operand);
......@@ -2248,6 +2248,23 @@ fn import(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*
22482248 return addZIRUnOp(mod, scope, src, .import, target);
22492249}
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
22512268fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst {
22522269 const tree = scope.tree();
22532270 const builtin_name = tree.tokenSlice(call.builtin_token);
......@@ -2267,6 +2284,8 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built
22672284 return simpleCast(mod, scope, rl, call, .intcast);
22682285 } else if (mem.eql(u8, builtin_name, "@bitCast")) {
22692286 return bitCast(mod, scope, rl, call);
2287 } else if (mem.eql(u8, builtin_name, "@TypeOf")) {
2288 return typeOf(mod, scope, rl, call);
22702289 } else if (mem.eql(u8, builtin_name, "@breakpoint")) {
22712290 const src = tree.token_locs[call.builtin_token].start;
22722291 return rlWrap(mod, scope, rl, try addZIRNoOp(mod, scope, src, .breakpoint));
......@@ -2344,7 +2363,7 @@ fn getSimplePrimitiveValue(name: []const u8) ?TypedValue {
23442363 return null;
23452364}
23462365
2347fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {
2366fn nodeMayNeedMemoryLocation(start_node: *ast.Node, scope: *Scope) bool {
23482367 var node = start_node;
23492368 while (true) {
23502369 switch (node.tag) {
......@@ -2468,10 +2487,120 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool {
24682487 .For,
24692488 .Switch,
24702489 .Call,
2471 .BuiltinCall, // TODO some of these can return false
24722490 .LabeledBlock,
24732491 => 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
24752604 // Depending on AST properties, they may need memory locations.
24762605 .If => return node.castTag(.If).?.@"else" != null,
24772606 }
src/zir.zig+12
......@@ -251,6 +251,8 @@ pub const Inst = struct {
251251 subwrap,
252252 /// Returns the type of a value.
253253 typeof,
254 /// Is the builtin @TypeOf which returns the type after peertype resolution of one or more params
255 typeof_peer,
254256 /// Asserts control-flow will not reach this instruction. Not safety checked - the compiler
255257 /// will assume the correctness of this instruction.
256258 unreach_nocheck,
......@@ -403,6 +405,7 @@ pub const Inst = struct {
403405 .error_set => ErrorSet,
404406 .slice => Slice,
405407 .switchbr => SwitchBr,
408 .typeof_peer => TypeOfPeer,
406409 };
407410 }
408411
......@@ -510,6 +513,7 @@ pub const Inst = struct {
510513 .slice_start,
511514 .import,
512515 .switch_range,
516 .typeof_peer,
513517 => false,
514518
515519 .@"break",
......@@ -1032,6 +1036,14 @@ pub const Inst = struct {
10321036 body: Module.Body,
10331037 };
10341038 };
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 };
10351047};
10361048
10371049pub const ErrorMsg = struct {
src/zir_sema.zig+16
......@@ -118,6 +118,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
118118 .iserr => return analyzeInstIsErr(mod, scope, old_inst.castTag(.iserr).?),
119119 .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?),
120120 .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?),
121 .typeof_peer => return analyzeInstTypeOfPeer(mod, scope, old_inst.castTag(.typeof_peer).?),
121122 .optional_type => return analyzeInstOptionalType(mod, scope, old_inst.castTag(.optional_type).?),
122123 .unwrap_optional_safe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_safe).?, true),
123124 .unwrap_optional_unsafe => return analyzeInstUnwrapOptional(mod, scope, old_inst.castTag(.unwrap_optional_unsafe).?, false),
......@@ -1663,6 +1664,11 @@ fn analyzeInstCmp(
16631664 // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
16641665 // numeric types.
16651666 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));
16661672 }
16671673 return mod.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{});
16681674}
......@@ -1672,6 +1678,16 @@ fn analyzeInstTypeOf(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerErr
16721678 return mod.constType(scope, inst.base.src, operand.ty);
16731679}
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
16751691fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
16761692 const uncasted_operand = try resolveInst(mod, scope, inst.positionals.operand);
16771693 const bool_type = Type.initTag(.bool);
test/stage2/test.zig+58
......@@ -370,6 +370,64 @@ pub fn addCases(ctx: *TestContext) !void {
370370 "",
371371 );
372372 }
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
374432 {
375433 var case = ctx.exe("assert function", linux_x64);