From a2230639232c069e4052a2e994dd5c0bd4e2517f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 31 Aug 2019 10:38:18 -0400 Subject: [PATCH] `@typeOf` now guarantees no runtime side effects related: #1627 --- doc/langref.html.in | 16 +++++++++++++ src/all_types.hpp | 8 +++++++ src/analyze.cpp | 22 ++++++++++++++++++ src/analyze.hpp | 2 ++ src/codegen.cpp | 7 ++++++ src/ir.cpp | 10 ++++++++- test/stage1/behavior/sizeof_and_typeof.zig | 26 ++++++++++++++++++++++ 7 files changed, 90 insertions(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index c1fe08ddb..44b225681 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8114,7 +8114,23 @@ pub const TypeInfo = union(TypeId) { This function returns a compile-time constant, which is the type of the expression passed as an argument. The expression is evaluated.

+

{#syntax#}@typeOf{#endsyntax#} guarantees no run-time side-effects within the expression:

+ {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; +test "no runtime side effects" { + var data: i32 = 0; + const T = @typeOf(foo(i32, &data)); + comptime assert(T == i32); + assert(data == 0); +} + +fn foo(comptime T: type, ptr: *T) T { + ptr.* += 1; + return ptr.*; +} + {#code_end#} {#header_close#} {#header_open|@unionInit#} diff --git a/src/all_types.hpp b/src/all_types.hpp index 87756d338..aee6d3994 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -2104,6 +2104,7 @@ enum ScopeId { ScopeIdFnDef, ScopeIdCompTime, ScopeIdRuntime, + ScopeIdTypeOf, }; struct Scope { @@ -2244,6 +2245,13 @@ struct ScopeFnDef { ZigFn *fn_entry; }; +// This scope is created for a @typeOf. +// All runtime side-effects are elided within it. +// NodeTypeFnCallExpr +struct ScopeTypeOf { + Scope base; +}; + // synchronized with code in define_builtin_compile_vars enum AtomicOrder { AtomicOrderUnordered, diff --git a/src/analyze.cpp b/src/analyze.cpp index 43c8d499d..df5b27784 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -197,6 +197,12 @@ Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent) { return &scope->base; } +Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent) { + ScopeTypeOf *scope = allocate(1); + init_scope(g, &scope->base, ScopeIdTypeOf, node, parent); + return &scope->base; +} + ZigType *get_scope_import(Scope *scope) { while (scope) { if (scope->id == ScopeIdDecls) { @@ -209,6 +215,22 @@ ZigType *get_scope_import(Scope *scope) { zig_unreachable(); } +ScopeTypeOf *get_scope_typeof(Scope *scope) { + while (scope) { + switch (scope->id) { + case ScopeIdTypeOf: + return reinterpret_cast(scope); + case ScopeIdFnDef: + case ScopeIdDecls: + return nullptr; + default: + scope = scope->parent; + continue; + } + } + zig_unreachable(); +} + static ZigType *new_container_type_entry(CodeGen *g, ZigTypeId id, AstNode *source_node, Scope *parent_scope, Buf *bare_name) { diff --git a/src/analyze.hpp b/src/analyze.hpp index 6e8897bf8..dc702167e 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -85,6 +85,7 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node); ZigFn *scope_fn_entry(Scope *scope); ZigPackage *scope_package(Scope *scope); ZigType *get_scope_import(Scope *scope); +ScopeTypeOf *get_scope_typeof(Scope *scope); void init_tld(Tld *tld, TldId id, Buf *name, VisibMod visib_mod, AstNode *source_node, Scope *parent_scope); ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf *name, bool is_const, ConstExprValue *init_value, Tld *src_tld, ZigType *var_type); @@ -112,6 +113,7 @@ ScopeSuspend *create_suspend_scope(CodeGen *g, AstNode *node, Scope *parent); ScopeFnDef *create_fndef_scope(CodeGen *g, AstNode *node, Scope *parent, ZigFn *fn_entry); Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent); Scope *create_runtime_scope(CodeGen *g, AstNode *node, Scope *parent, IrInstruction *is_comptime); +Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent); void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str); ConstExprValue *create_const_str_lit(CodeGen *g, Buf *str); diff --git a/src/codegen.cpp b/src/codegen.cpp index d87b5d0ae..33713a9b3 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -645,6 +645,7 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) { case ScopeIdSuspend: case ScopeIdCompTime: case ScopeIdRuntime: + case ScopeIdTypeOf: return get_di_scope(g, scope->parent); } zig_unreachable(); @@ -3757,6 +3758,7 @@ static void render_async_var_decls(CodeGen *g, Scope *scope) { case ScopeIdSuspend: case ScopeIdCompTime: case ScopeIdRuntime: + case ScopeIdTypeOf: scope = scope->parent; continue; } @@ -5942,12 +5944,17 @@ static void ir_render(CodeGen *g, ZigFn *fn_entry) { for (size_t block_i = 0; block_i < executable->basic_block_list.length; block_i += 1) { IrBasicBlock *current_block = executable->basic_block_list.at(block_i); + if (get_scope_typeof(current_block->scope) != nullptr) { + LLVMBuildBr(g->builder, current_block->llvm_block); + } assert(current_block->llvm_block); LLVMPositionBuilderAtEnd(g->builder, current_block->llvm_block); for (size_t instr_i = 0; instr_i < current_block->instruction_list.length; instr_i += 1) { IrInstruction *instruction = current_block->instruction_list.at(instr_i); if (instruction->ref_count == 0 && !ir_has_side_effects(instruction)) continue; + if (get_scope_typeof(instruction->scope) != nullptr) + continue; if (!g->strip_debug_symbols) { set_debug_location(g, instruction); diff --git a/src/ir.cpp b/src/ir.cpp index 02a134c62..ad81b27a9 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -3344,6 +3344,7 @@ static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_sco case ScopeIdSuspend: case ScopeIdCompTime: case ScopeIdRuntime: + case ScopeIdTypeOf: scope = scope->parent; continue; case ScopeIdDeferExpr: @@ -3399,6 +3400,7 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o case ScopeIdSuspend: case ScopeIdCompTime: case ScopeIdRuntime: + case ScopeIdTypeOf: scope = scope->parent; continue; case ScopeIdDeferExpr: @@ -4379,8 +4381,10 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo zig_unreachable(); case BuiltinFnIdTypeof: { + Scope *sub_scope = create_typeof_scope(irb->codegen, node, scope); + AstNode *arg_node = node->data.fn_call_expr.params.at(0); - IrInstruction *arg = ir_gen_node(irb, arg_node, scope); + IrInstruction *arg = ir_gen_node(irb, arg_node, sub_scope); if (arg == irb->codegen->invalid_instruction) return arg; @@ -8269,6 +8273,10 @@ static ConstExprValue *ir_exec_const_result(CodeGen *codegen, IrExecutable *exec break; } } + if (get_scope_typeof(instruction->scope) != nullptr) { + // doesn't count, it's inside a @typeOf() + continue; + } exec_add_error_node(codegen, exec, instruction->source_node, buf_sprintf("unable to evaluate constant expression")); return &codegen->invalid_instruction->value; diff --git a/test/stage1/behavior/sizeof_and_typeof.zig b/test/stage1/behavior/sizeof_and_typeof.zig index da79c3a27..6f57bfedd 100644 --- a/test/stage1/behavior/sizeof_and_typeof.zig +++ b/test/stage1/behavior/sizeof_and_typeof.zig @@ -89,3 +89,29 @@ test "@sizeOf(T) == 0 doesn't force resolving struct size" { expect(@sizeOf(S.Foo) == 4); expect(@sizeOf(S.Bar) == 8); } + +test "@typeOf() has no runtime side effects" { + const S = struct { + fn foo(comptime T: type, ptr: *T) T { + ptr.* += 1; + return ptr.*; + } + }; + var data: i32 = 0; + const T = @typeOf(S.foo(i32, &data)); + comptime expect(T == i32); + expect(data == 0); +} + +test "branching logic inside @typeOf" { + const S = struct { + var data: i32 = 0; + fn foo() anyerror!i32 { + data += 1; + return undefined; + } + }; + const T = @typeOf(S.foo() catch undefined); + comptime expect(T == i32); + expect(S.data == 0); +}