@typeOf now guarantees no runtime side effects

related: #1627
This commit is contained in:
Andrew Kelley 2019-08-31 10:38:18 -04:00
parent 6ab8b2aab4
commit a223063923
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
7 changed files with 90 additions and 1 deletions

View File

@ -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.
</p>
<p>{#syntax#}@typeOf{#endsyntax#} guarantees no run-time side-effects within the expression:</p>
{#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#}

View File

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

View File

@ -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<ScopeTypeOf>(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<ScopeTypeOf *>(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)
{

View File

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

View File

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

View File

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

View File

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