mirror of
https://github.com/odin-lang/Odin.git
synced 2026-10-08 13:51:37 -04:00
Tidy up check_escape.cpp
This commit is contained in:
1 parent
3f1a87dd33
commit
cfde9fb9cb
2 files changed
+210
-267
No files matched your search
+209
-266
@@ -152,6 +152,38 @@ struct EscapeNilUse {
|
||||
bool nil;
|
||||
};
|
||||
|
||||
// The procedures are analysed in the strongly connected components of what they may call, callees first,
|
||||
// and those calling each other until what flows through them settles, so what is found never depends on the threads
|
||||
struct EscapeGraph {
|
||||
Array<ProcInfo *> procs; // a procedure's index is kept on its decl, see `DeclInfo::escape_index`
|
||||
Array<i32> offsets; // procedure -> the procedures it may call, as `targets[offsets[v]..<offsets[v+1]]`
|
||||
Array<i32> targets;
|
||||
Array<i32> caller_offsets; // procedure -> the procedures which may call it, as `callers[caller_offsets[v]..<caller_offsets[v+1]]`, when threaded
|
||||
Array<i32> callers;
|
||||
Array<i32> group_of;
|
||||
Array<i32> group_offsets; // group -> its procedures, as `members[group_offsets[gi]..<group_offsets[gi+1]]`
|
||||
Array<i32> members; // every group a group may call has a lower index
|
||||
Slice<std::atomic<i32> > pending; // per group its calls to procedures of other groups which are not analysed yet
|
||||
Slice<std::atomic<i32> > cursors; // per procedure while `targets` is filled
|
||||
bool threaded;
|
||||
};
|
||||
|
||||
struct EscapeMemberTask {
|
||||
EscapeGraph * graph;
|
||||
i32 v;
|
||||
bool stale; // as the flows of one it calls changed
|
||||
Slice<EscapeFlow> flows;
|
||||
Array<EscapeReport> reports; // of its latest analysis
|
||||
Array<i32> callees; // the members of the group it calls, by their index
|
||||
Array<i32> callers;
|
||||
};
|
||||
|
||||
// of a walk through the members of a group calling each other
|
||||
struct EscapeOrderFrame {
|
||||
i32 k;
|
||||
isize next;
|
||||
};
|
||||
|
||||
struct EscapeAnalysis {
|
||||
TypeProc * pt;
|
||||
Entity * variadic;
|
||||
@@ -162,16 +194,35 @@ struct EscapeAnalysis {
|
||||
Array<EscapeAlias> aliases;
|
||||
Array<Ast *> reported;
|
||||
Array<EscapeFlow> flows;
|
||||
struct EscapeGraph * graph;
|
||||
i32 group; // of the procedures being analysed together, see `EscapeGraph`
|
||||
Array<EscapeReport> *reports; // kept rather than reported, while what flows through the group may still change
|
||||
bool muted; // the entry point of an executable, which only returns as the program ends
|
||||
EscapeGraph * graph;
|
||||
i32 group; // of the procedures being analysed together, see `EscapeGraph`
|
||||
Array<EscapeReport> *reports; // kept rather than reported, while what flows through the group may still change
|
||||
bool muted; // the entry point of an executable, which only returns as the program ends
|
||||
|
||||
bool nil_deref; // -vet-nil-deref
|
||||
Array<EscapeNilUse> nil_uses;
|
||||
Array<Entity *> nil_escaped; // locals whose address was taken, which anything may change from then on
|
||||
};
|
||||
|
||||
gb_global EscapeGraph escape_graph;
|
||||
|
||||
|
||||
gb_internal EscapeValue escape_expr (EscapeAnalysis *ea, Ast *expr);
|
||||
gb_internal EscapeValue escape_addr (EscapeAnalysis *ea, Ast *expr);
|
||||
gb_internal EscapeValue escape_ident_value (EscapeAnalysis *ea, Entity *e);
|
||||
gb_internal Array<EscapeValue> escape_call (EscapeAnalysis *ea, Ast *call);
|
||||
gb_internal Ast * escape_call_of (Ast *expr);
|
||||
gb_internal void escape_store_through (EscapeAnalysis *ea, Ast *node, EscapeValue const &ptr, EscapeValue const &v, EscapeUpdateKind update);
|
||||
gb_internal bool escape_is_explicit_unsafe_conversion(Type *t);
|
||||
gb_internal void escape_nil_scan (EscapeAnalysis *ea, Ast *expr);
|
||||
gb_internal void escape_stmt (EscapeAnalysis *ea, Ast *node);
|
||||
gb_internal void escape_branch (EscapeAnalysis *ea, Ast *label, TokenKind kind);
|
||||
gb_internal void escape_exit (EscapeAnalysis *ea, Ast *node, Slice<Ast *> const &results, Array<EscapeValue> const &values);
|
||||
|
||||
// see checker_global.cpp
|
||||
gb_internal i32 global_graph_scc(i32 node_count, Array<i32> const &offsets, Array<i32> const &targets, Array<i32> *comp_of_);
|
||||
gb_internal void global_graph_csr(i32 node_count, Array<i32> const &edge_from, Array<i32> const &edge_to, Array<i32> *offsets, Array<i32> *targets);
|
||||
|
||||
|
||||
gb_internal bool escape_path_has_prefix(EscapePath const &p, EscapePath const &prefix) {
|
||||
if (prefix.count > p.count) {
|
||||
@@ -190,15 +241,6 @@ gb_internal bool escape_path_eq(EscapePath const &a, EscapePath const &b) {
|
||||
return a.count == b.count && escape_path_has_prefix(a, b);
|
||||
}
|
||||
|
||||
gb_internal bool escape_path_has(EscapePath const &p, EscapeStepKind kind) {
|
||||
for (EscapeStep const &step : p) {
|
||||
if (step.kind == kind) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
gb_internal EscapePath escape_path_of(EscapeStepKind kind, Entity *field=nullptr) {
|
||||
auto p = slice_make<EscapeStep>(temporary_allocator(), 1);
|
||||
p[0] = {kind, field};
|
||||
@@ -618,48 +660,13 @@ gb_internal Type *escape_pointee_type(Type *t) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// whether what a value of the type points to cannot hold pointers, e.g. the bytes of a `[]u8`
|
||||
gb_internal bool escape_points_to_plain(Type *t) {
|
||||
Type *pointee = escape_pointee_type(t);
|
||||
return pointee != nullptr && !escape_type_has_pointers(pointee);
|
||||
}
|
||||
|
||||
// a store to a field overwrites only that part of a fact for more of the value, so it is kept for the other fields
|
||||
gb_internal bool escape_split_fact(EscapeFact const &f, Type *type, EscapePath const &path, Array<EscapeFact> *pieces) {
|
||||
auto split = array_make<EscapeFact>(temporary_allocator(), 0, 0);
|
||||
Type *t = type;
|
||||
for_array(i, path) {
|
||||
EscapeStep const &step = path[i];
|
||||
Type *bt = base_type(t);
|
||||
if (step.kind != EscapeStep_Field || bt == nullptr || bt->kind != Type_Struct) {
|
||||
return false;
|
||||
}
|
||||
bool is_own_field = false;
|
||||
for (Entity *field : bt->Struct.fields) {
|
||||
is_own_field |= field == step.field;
|
||||
}
|
||||
if (!is_own_field) {
|
||||
// e.g. a field reached through 'using'
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i >= f.path.count) {
|
||||
for (Entity *field : bt->Struct.fields) {
|
||||
if (field == step.field || !escape_type_has_pointers(field->type)) {
|
||||
continue;
|
||||
}
|
||||
EscapePath piece = escape_path_concat(slice(path, 0, i), escape_path_of(EscapeStep_Field, field));
|
||||
array_add(&split, EscapeFact{f.obj, piece, escape_origin_within(f.origin, piece, f.path.count)});
|
||||
}
|
||||
}
|
||||
t = step.field->type;
|
||||
}
|
||||
array_add_elems(pieces, split.data, split.count);
|
||||
return true;
|
||||
}
|
||||
|
||||
gb_internal void escape_store_obj(EscapeAnalysis *ea, EscapeObject const &obj, EscapePath const &path, EscapeValue const &v, EscapeUpdateKind update) {
|
||||
if (update == EscapeUpdate_Replace && !escape_path_has(path, EscapeStep_AnyElement)) {
|
||||
// a store to any element of an array leaves the others
|
||||
bool replaces = update == EscapeUpdate_Replace;
|
||||
for (EscapeStep const &step : path) {
|
||||
replaces &= step.kind != EscapeStep_AnyElement;
|
||||
}
|
||||
if (replaces) {
|
||||
Type *type = escape_type_at(obj, {});
|
||||
auto pieces = array_make<EscapeFact>(temporary_allocator(), 0, 0);
|
||||
for (isize i = ea->state.facts.count-1; i >= 0; i--) {
|
||||
@@ -669,7 +676,43 @@ gb_internal void escape_store_obj(EscapeAnalysis *ea, EscapeObject const &obj, E
|
||||
}
|
||||
if (escape_path_has_prefix(f.path, path)) {
|
||||
array_unordered_remove(&ea->state.facts, i);
|
||||
} else if (escape_path_has_prefix(path, f.path) && escape_split_fact(f, type, path, &pieces)) {
|
||||
continue;
|
||||
}
|
||||
if (!escape_path_has_prefix(path, f.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// a store to a field overwrites only that part of a fact for more of the value, so it is kept for the other fields
|
||||
auto split = array_make<EscapeFact>(temporary_allocator(), 0, 0);
|
||||
bool splits = true;
|
||||
Type *t = type;
|
||||
for (isize j = 0; j < path.count; j++) {
|
||||
EscapeStep const &step = path[j];
|
||||
Type *bt = base_type(t);
|
||||
// through the fields of structs, not e.g. a field reached through 'using'
|
||||
splits = false;
|
||||
if (step.kind == EscapeStep_Field && bt != nullptr && bt->kind == Type_Struct) {
|
||||
for (Entity *field : bt->Struct.fields) {
|
||||
splits |= field == step.field;
|
||||
}
|
||||
}
|
||||
if (!splits) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (j >= f.path.count) {
|
||||
for (Entity *field : bt->Struct.fields) {
|
||||
if (field == step.field || !escape_type_has_pointers(field->type)) {
|
||||
continue;
|
||||
}
|
||||
EscapePath piece = escape_path_concat(slice(path, 0, j), escape_path_of(EscapeStep_Field, field));
|
||||
array_add(&split, EscapeFact{f.obj, piece, escape_origin_within(f.origin, piece, f.path.count)});
|
||||
}
|
||||
}
|
||||
t = step.field->type;
|
||||
}
|
||||
if (splits) {
|
||||
array_add_elems(&pieces, split.data, split.count);
|
||||
array_unordered_remove(&ea->state.facts, i);
|
||||
}
|
||||
}
|
||||
@@ -743,13 +786,6 @@ gb_internal EscapeValue escape_load(EscapeAnalysis *ea, EscapeValue const &ptr,
|
||||
return v;
|
||||
}
|
||||
|
||||
gb_internal EscapeValue escape_expr(EscapeAnalysis *ea, Ast *expr);
|
||||
gb_internal EscapeValue escape_addr(EscapeAnalysis *ea, Ast *expr);
|
||||
|
||||
gb_internal Array<EscapeValue> escape_call(EscapeAnalysis *ea, Ast *call);
|
||||
gb_internal Ast *escape_call_of(Ast *expr);
|
||||
gb_internal void escape_store_through(EscapeAnalysis *ea, Ast *node, EscapeValue const &ptr, EscapeValue const &v, EscapeUpdateKind update);
|
||||
|
||||
gb_internal void escape_visit_exits(EscapeAnalysis *ea, Ast *expr) {
|
||||
if (expr == nullptr) {
|
||||
return;
|
||||
@@ -858,10 +894,6 @@ gb_internal EscapeValue escape_elems_of(EscapeAnalysis *ea, Ast *x) {
|
||||
return escape_elems_at(ea, escape_addr(ea, x), t);
|
||||
}
|
||||
|
||||
gb_internal void escape_stmt (EscapeAnalysis *ea, Ast *node);
|
||||
gb_internal void escape_branch(EscapeAnalysis *ea, Ast *label, TokenKind kind);
|
||||
gb_internal void escape_exit (EscapeAnalysis *ea, Ast *node, Slice<Ast *> const &results, Array<EscapeValue> const &values);
|
||||
|
||||
gb_internal bool escape_selector_path(Ast *se_node, EscapePath *path) {
|
||||
ast_node(se, SelectorExpr, se_node);
|
||||
if (se->swizzle_count > 0 || se->is_bit_field) {
|
||||
@@ -1128,8 +1160,6 @@ gb_internal void escape_nil_restore(EscapeAnalysis *ea, Array<EscapeNil> const &
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal bool escape_is_explicit_unsafe_conversion(Type *t);
|
||||
|
||||
// the paths within the value of an expression which are nil on every path reaching here
|
||||
gb_internal Array<EscapePath> escape_nil_paths(EscapeAnalysis *ea, Ast *expr) {
|
||||
auto paths = array_make<EscapePath>(temporary_allocator(), 0, 0);
|
||||
@@ -1157,25 +1187,21 @@ gb_internal Array<EscapePath> escape_nil_paths(EscapeAnalysis *ea, Ast *expr) {
|
||||
return paths;
|
||||
}
|
||||
auto values = array_make<Ast *>(temporary_allocator(), t->Struct.fields.count);
|
||||
for (Ast *&value : values) {
|
||||
value = nullptr;
|
||||
}
|
||||
for_array(i, cl->elems) {
|
||||
Ast *elem = cl->elems[i];
|
||||
isize index = -1;
|
||||
if (elem->kind != Ast_FieldValue) {
|
||||
index = i;
|
||||
} else if (elem->FieldValue.field->kind == Ast_Ident) {
|
||||
isize index = i;
|
||||
bool found = elem->kind != Ast_FieldValue && i < values.count;
|
||||
if (elem->kind == Ast_FieldValue && elem->FieldValue.field->kind == Ast_Ident) {
|
||||
for_array(j, t->Struct.fields) {
|
||||
if (t->Struct.fields[j]->token.string == elem->FieldValue.field->Ident.token.string) {
|
||||
index = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
elem = elem->FieldValue.value;
|
||||
}
|
||||
if (index < 0 || index >= values.count) {
|
||||
if (!found) {
|
||||
// e.g. a field reached through 'using'
|
||||
array_clear(&paths);
|
||||
return paths;
|
||||
}
|
||||
values[index] = elem;
|
||||
@@ -1331,8 +1357,6 @@ gb_internal void escape_nil_use(EscapeAnalysis *ea, Ast *ptr, EscapeReportKind k
|
||||
array_add(&ea->nil_uses, EscapeNilUse{ptr, kind, nil});
|
||||
}
|
||||
|
||||
gb_internal void escape_nil_scan(EscapeAnalysis *ea, Ast *expr);
|
||||
|
||||
// the address of what an expression is, which only loads what it is reached through
|
||||
gb_internal void escape_nil_scan_addr(EscapeAnalysis *ea, Ast *expr) {
|
||||
expr = unparen_expr(expr);
|
||||
@@ -1564,8 +1588,6 @@ gb_internal EscapeValue escape_convert(EscapeAnalysis *ea, EscapeValue const &v,
|
||||
return escape_value_of(EscapeOrigin_Temp, {nullptr, expr}, {});
|
||||
}
|
||||
|
||||
gb_internal EscapeValue escape_ident_value(EscapeAnalysis *ea, Entity *e);
|
||||
|
||||
gb_internal EscapeValue escape_ident_addr(EscapeAnalysis *ea, Entity *e) {
|
||||
if (e == nullptr || e->kind != Entity_Variable) {
|
||||
return escape_value();
|
||||
@@ -1712,8 +1734,6 @@ gb_internal bool escape_is_explicit_unsafe_conversion(Type *t) {
|
||||
return t != nullptr && (is_type_rawptr(t) || is_type_uintptr(t));
|
||||
}
|
||||
|
||||
gb_internal Slice<EscapeFlow> escape_flows_of(EscapeAnalysis *ea, Entity *e);
|
||||
|
||||
// loading through the pointers once, as the callee does, where a dynamic array or map holds the pointer to its buffer
|
||||
gb_internal EscapeValue escape_load_once(EscapeAnalysis *ea, EscapeValue const &ptr) {
|
||||
EscapeValue v = escape_value();
|
||||
@@ -1780,27 +1800,6 @@ gb_internal EscapeValue escape_arg_pointers(EscapeValue const &arg, Type *type,
|
||||
return escape_pointer_offset(escape_as_pointer(escape_value_project(arg, value_path)), offset);
|
||||
}
|
||||
|
||||
gb_internal bool escape_param_index_of_arg(TypeProc *pt, Ast *arg, isize position, isize *index) {
|
||||
if (arg->kind != Ast_FieldValue) {
|
||||
*index = position;
|
||||
if (pt->variadic && position > pt->variadic_index) {
|
||||
*index = pt->variadic_index;
|
||||
}
|
||||
return *index < pt->param_count;
|
||||
}
|
||||
Ast *name = arg->FieldValue.field;
|
||||
if (name->kind != Ast_Ident || pt->params == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for_array(i, pt->params->Tuple.variables) {
|
||||
if (pt->params->Tuple.variables[i]->token.string == name->Ident.token.string) {
|
||||
*index = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
gb_internal Array<EscapeValue> escape_call(EscapeAnalysis *ea, Ast *call) {
|
||||
ast_node(ce, CallExpr, call);
|
||||
escape_visit_exits(ea, ce->proc);
|
||||
@@ -1834,7 +1833,9 @@ gb_internal Array<EscapeValue> escape_call(EscapeAnalysis *ea, Ast *call) {
|
||||
EscapeValue dst = escape_as_pointer(escape_expr(ea, ce->args[0]));
|
||||
EscapeValue src = escape_as_pointer(escape_expr(ea, ce->args[1]));
|
||||
escape_visit_exits(ea, ce->args[2]);
|
||||
if (!escape_points_to_plain(ce->args[1]->tav.type)) {
|
||||
// unless what it copies cannot hold pointers, e.g. bytes
|
||||
Type *pointee = escape_pointee_type(ce->args[1]->tav.type);
|
||||
if (pointee == nullptr || escape_type_has_pointers(pointee)) {
|
||||
escape_store_through(ea, call, dst, escape_load(ea, src, nullptr), EscapeUpdate_Add);
|
||||
}
|
||||
return results;
|
||||
@@ -1850,7 +1851,17 @@ gb_internal Array<EscapeValue> escape_call(EscapeAnalysis *ea, Ast *call) {
|
||||
Type *t = base_type(proc_entity_full_type(e));
|
||||
if (t != nullptr && t->kind == Type_Proc) {
|
||||
pt = &t->Proc;
|
||||
flows = escape_flows_of(ea, e);
|
||||
|
||||
// none for a foreign procedure or one whose body was not checked, and what it has so far while its group settles
|
||||
DeclInfo *d = e->decl_info;
|
||||
bool known = d != nullptr && d->proc_info != nullptr;
|
||||
if (known && !d->escapes_analysed.load()) {
|
||||
// otherwise a call the checker did not record, which should not happen
|
||||
known = ea->graph->group_of[d->escape_index] == ea->group;
|
||||
}
|
||||
if (known) {
|
||||
flows = d->escape_flows;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flows.count == 0) {
|
||||
@@ -1872,10 +1883,8 @@ gb_internal Array<EscapeValue> escape_call(EscapeAnalysis *ea, Ast *call) {
|
||||
auto args = array_make<EscapeValue>(temporary_allocator(), pt->param_count);
|
||||
auto types = array_make<Type *>(temporary_allocator(), pt->param_count);
|
||||
auto given = array_make<bool>(temporary_allocator(), pt->param_count);
|
||||
for_array(i, args) {
|
||||
args[i] = escape_value();
|
||||
types[i] = nullptr;
|
||||
given[i] = false;
|
||||
for (EscapeValue &arg : args) {
|
||||
arg = escape_value();
|
||||
}
|
||||
|
||||
bool packs_variadic = pt->variadic && !pt->c_vararg && ce->ellipsis.pos.line == 0;
|
||||
@@ -1886,9 +1895,26 @@ gb_internal Array<EscapeValue> escape_call(EscapeAnalysis *ea, Ast *call) {
|
||||
value = arg->FieldValue.value;
|
||||
}
|
||||
|
||||
isize index = 0;
|
||||
// the parameter it is passed to
|
||||
isize index = i;
|
||||
bool has_param = false;
|
||||
if (arg->kind != Ast_FieldValue) {
|
||||
if (pt->variadic && i > pt->variadic_index) {
|
||||
index = pt->variadic_index;
|
||||
}
|
||||
has_param = index < pt->param_count;
|
||||
} else if (arg->FieldValue.field->kind == Ast_Ident && pt->params != nullptr) {
|
||||
for_array(j, pt->params->Tuple.variables) {
|
||||
if (pt->params->Tuple.variables[j]->token.string == arg->FieldValue.field->Ident.token.string) {
|
||||
index = j;
|
||||
has_param = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool involved = false;
|
||||
if (escape_param_index_of_arg(pt, arg, i, &index)) {
|
||||
if (has_param) {
|
||||
for (EscapeFlow const &flow : flows) {
|
||||
involved |= flow.param == index;
|
||||
switch (flow.target) {
|
||||
@@ -2441,31 +2467,6 @@ gb_internal void escape_report_value(EscapeAnalysis *ea, Ast *node, Ast *expr, S
|
||||
}
|
||||
}
|
||||
|
||||
// a use is reported only when what it goes through is nil every time it is reached, e.g. in a defer run at several exits
|
||||
gb_internal void escape_nil_report(EscapeAnalysis *ea) {
|
||||
array_sort(ea->nil_uses, escape_nil_use_cmp);
|
||||
for (isize i = 0; i < ea->nil_uses.count; /**/) {
|
||||
EscapeNilUse const &use = ea->nil_uses[i];
|
||||
bool nil = true;
|
||||
isize j = i;
|
||||
for (/**/; j < ea->nil_uses.count && ea->nil_uses[j].ptr == use.ptr; j++) {
|
||||
nil &= ea->nil_uses[j].nil;
|
||||
}
|
||||
if (nil) {
|
||||
gbString s = escape_expr_to_string(use.ptr);
|
||||
EscapeReport r = {use.kind, use.ptr, make_string_c(s)};
|
||||
if (ea->reports == nullptr) {
|
||||
escape_report_emit(r);
|
||||
} else {
|
||||
r.expr_str = copy_string(permanent_allocator(), r.expr_str);
|
||||
array_add(ea->reports, r);
|
||||
}
|
||||
gb_string_free(s);
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal void escape_run_defers(EscapeAnalysis *ea, isize depth) {
|
||||
for (isize i = ea->defers.count-1; i >= depth; i--) {
|
||||
Array<Ast *> defers = array_clone(temporary_allocator(), ea->defers[i]);
|
||||
@@ -2552,34 +2553,6 @@ gb_internal void escape_add_result_flows(EscapeAnalysis *ea, isize result_index,
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal void escape_add_store_flow(EscapeAnalysis *ea, EscapeOuterStore const &s) {
|
||||
EscapeFlow flow = {};
|
||||
if (!escape_flow_from(ea->pt, s.origin, &flow)) {
|
||||
return;
|
||||
}
|
||||
switch (s.dest.kind) {
|
||||
case EscapeOrigin_Outer: flow.target = EscapeFlowTarget_Outer; break;
|
||||
case EscapeOrigin_Param: flow.target = EscapeFlowTarget_Pointee; break;
|
||||
case EscapeOrigin_ParamLoad: flow.target = EscapeFlowTarget_Loaded; break;
|
||||
case EscapeOrigin_ParamDeep: flow.target = EscapeFlowTarget_Deep; break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
if (flow.target != EscapeFlowTarget_Outer) {
|
||||
flow.target_path = s.dest.path;
|
||||
if (!escape_param_index(ea->pt, s.dest.obj.entity, &flow.target_index)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (flow.target == EscapeFlowTarget_Pointee &&
|
||||
flow.target_index == flow.param &&
|
||||
escape_path_eq(flow.target_path, flow.param_path)) {
|
||||
// storing it back into the memory it came from cannot make anything outlive it
|
||||
return;
|
||||
}
|
||||
escape_add_flow(ea, flow);
|
||||
}
|
||||
|
||||
gb_internal void escape_exit(EscapeAnalysis *ea, Ast *node, Slice<Ast *> const &results, Array<EscapeValue> const &values) {
|
||||
TypeProc *pt = ea->pt;
|
||||
isize result_count = 0;
|
||||
@@ -2587,12 +2560,9 @@ gb_internal void escape_exit(EscapeAnalysis *ea, Ast *node, Slice<Ast *> const &
|
||||
result_count = pt->results->Tuple.variables.count;
|
||||
}
|
||||
|
||||
// what is returned is fixed before the defers run, and is what the named results hold for them
|
||||
if (values.count == result_count) {
|
||||
escape_nil_forget_results(ea);
|
||||
}
|
||||
|
||||
// what is returned is fixed before the defers run
|
||||
if (values.count == result_count) {
|
||||
for (isize i = 0; i < result_count; i++) {
|
||||
Ast *result = results[0]; // the single call of several results
|
||||
if (results.count == values.count) {
|
||||
@@ -2613,8 +2583,6 @@ gb_internal void escape_exit(EscapeAnalysis *ea, Ast *node, Slice<Ast *> const &
|
||||
escape_run_defers(ea, 0);
|
||||
|
||||
for (EscapeOuterStore const &s : ea->state.outers) {
|
||||
escape_add_store_flow(ea, s);
|
||||
|
||||
EscapeValue v = escape_value();
|
||||
escape_value_add(&v, {}, s.origin);
|
||||
EscapeOrigin o = {};
|
||||
@@ -2627,6 +2595,33 @@ gb_internal void escape_exit(EscapeAnalysis *ea, Ast *node, Slice<Ast *> const &
|
||||
escape_report(ea, s.node, make_string_c(str), o, kind);
|
||||
gb_string_free(str);
|
||||
}
|
||||
|
||||
// what of the caller's memory it stores, and where, for the callers
|
||||
EscapeFlow flow = {};
|
||||
if (!escape_flow_from(ea->pt, s.origin, &flow)) {
|
||||
continue;
|
||||
}
|
||||
switch (s.dest.kind) {
|
||||
case EscapeOrigin_Outer: flow.target = EscapeFlowTarget_Outer; break;
|
||||
case EscapeOrigin_Param: flow.target = EscapeFlowTarget_Pointee; break;
|
||||
case EscapeOrigin_ParamLoad: flow.target = EscapeFlowTarget_Loaded; break;
|
||||
case EscapeOrigin_ParamDeep: flow.target = EscapeFlowTarget_Deep; break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (flow.target != EscapeFlowTarget_Outer) {
|
||||
flow.target_path = s.dest.path;
|
||||
if (!escape_param_index(ea->pt, s.dest.obj.entity, &flow.target_index)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (flow.target == EscapeFlowTarget_Pointee &&
|
||||
flow.target_index == flow.param &&
|
||||
escape_path_eq(flow.target_path, flow.param_path)) {
|
||||
// storing it back into the memory it came from cannot make anything outlive it
|
||||
continue;
|
||||
}
|
||||
escape_add_flow(ea, flow);
|
||||
}
|
||||
ea->state.reachable = false;
|
||||
}
|
||||
@@ -3382,28 +3377,6 @@ gb_internal void escape_stmt(EscapeAnalysis *ea, Ast *node) {
|
||||
}
|
||||
|
||||
|
||||
// The procedures are analysed in the strongly connected components of what they may call, callees first,
|
||||
// and those calling each other until what flows through them settles, so what is found never depends on the threads
|
||||
|
||||
gb_internal i32 global_graph_scc(i32 node_count, Array<i32> const &offsets, Array<i32> const &targets, Array<i32> *comp_of_);
|
||||
gb_internal void global_graph_csr(i32 node_count, Array<i32> const &edge_from, Array<i32> const &edge_to, Array<i32> *offsets, Array<i32> *targets);
|
||||
|
||||
struct EscapeGraph {
|
||||
Array<ProcInfo *> procs; // a procedure's index is kept on its decl, see `DeclInfo::escape_index`
|
||||
Array<i32> offsets; // procedure -> the procedures it may call, as `targets[offsets[v]..<offsets[v+1]]`
|
||||
Array<i32> targets;
|
||||
Array<i32> caller_offsets; // procedure -> the procedures which may call it, as `callers[caller_offsets[v]..<caller_offsets[v+1]]`, when threaded
|
||||
Array<i32> callers;
|
||||
Array<i32> group_of;
|
||||
Array<i32> group_offsets; // group -> its procedures, as `members[group_offsets[gi]..<group_offsets[gi+1]]`
|
||||
Array<i32> members; // every group a group may call has a lower index
|
||||
std::atomic<i32> * pending; // per group its calls to procedures of other groups which are not analysed yet
|
||||
std::atomic<i32> * cursors; // per procedure while `targets` is filled
|
||||
bool threaded;
|
||||
};
|
||||
|
||||
gb_global EscapeGraph escape_graph;
|
||||
|
||||
gb_internal ErrorInstantiations escape_instantiations_of(ProcInfo *pi) {
|
||||
return {pi->generated_from_polymorphic ? pi : pi->poly_parent, nullptr};
|
||||
}
|
||||
@@ -3457,7 +3430,28 @@ gb_internal Slice<EscapeFlow> escape_analyse(EscapeGraph *g, i32 v, Array<Escape
|
||||
escape_exit(&ea, body, {}, {});
|
||||
}
|
||||
if (ea.nil_deref) {
|
||||
escape_nil_report(&ea);
|
||||
// a use is reported only when what it goes through is nil every time it is reached, e.g. in a defer run at several exits
|
||||
array_sort(ea.nil_uses, escape_nil_use_cmp);
|
||||
for (isize i = 0; i < ea.nil_uses.count; /**/) {
|
||||
EscapeNilUse const &use = ea.nil_uses[i];
|
||||
bool nil = true;
|
||||
isize j = i;
|
||||
for (/**/; j < ea.nil_uses.count && ea.nil_uses[j].ptr == use.ptr; j++) {
|
||||
nil &= ea.nil_uses[j].nil;
|
||||
}
|
||||
if (nil) {
|
||||
gbString str = escape_expr_to_string(use.ptr);
|
||||
EscapeReport r = {use.kind, use.ptr, make_string_c(str)};
|
||||
if (reports == nullptr) {
|
||||
escape_report_emit(r);
|
||||
} else {
|
||||
r.expr_str = copy_string(permanent_allocator(), r.expr_str);
|
||||
array_add(reports, r);
|
||||
}
|
||||
gb_string_free(str);
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
global_error_context.instantiations = prev_instantiations;
|
||||
|
||||
@@ -3471,35 +3465,6 @@ gb_internal Slice<EscapeFlow> escape_analyse(EscapeGraph *g, i32 v, Array<Escape
|
||||
return flows;
|
||||
}
|
||||
|
||||
gb_internal bool escape_flows_eq(Slice<EscapeFlow> const &a, Slice<EscapeFlow> const &b) {
|
||||
if (a.count != b.count) {
|
||||
return false;
|
||||
}
|
||||
for (EscapeFlow const &f : a) {
|
||||
bool found = false;
|
||||
for (EscapeFlow const &g : b) {
|
||||
if (escape_flow_eq(f, g)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct EscapeMemberTask {
|
||||
EscapeGraph * graph;
|
||||
i32 v;
|
||||
bool stale; // as the flows of one it calls changed
|
||||
Slice<EscapeFlow> flows;
|
||||
Array<EscapeReport> reports; // of its latest analysis
|
||||
Array<i32> callees; // the members of the group it calls, by their index
|
||||
Array<i32> callers;
|
||||
};
|
||||
|
||||
gb_internal WORKER_TASK_PROC(escape_member_worker) {
|
||||
EscapeMemberTask *t = cast(EscapeMemberTask *)data;
|
||||
array_clear(&t->reports);
|
||||
@@ -3527,7 +3492,16 @@ gb_internal void escape_analyse_members(EscapeGraph *g, Slice<EscapeMemberTask>
|
||||
gb_internal void escape_update_member(EscapeGraph *g, Slice<EscapeMemberTask> tasks, isize k) {
|
||||
EscapeMemberTask &t = tasks[k];
|
||||
DeclInfo *d = g->procs[t.v]->decl;
|
||||
if (escape_flows_eq(t.flows, d->escape_flows)) {
|
||||
|
||||
// the same flows, in any order
|
||||
bool same = t.flows.count == d->escape_flows.count;
|
||||
for (isize i = 0; same && i < t.flows.count; i++) {
|
||||
same = false;
|
||||
for (EscapeFlow const &other : d->escape_flows) {
|
||||
same |= escape_flow_eq(t.flows[i], other);
|
||||
}
|
||||
}
|
||||
if (same) {
|
||||
return;
|
||||
}
|
||||
d->escape_flows = t.flows;
|
||||
@@ -3579,31 +3553,24 @@ gb_internal void escape_analyse_group(EscapeGraph *g, i32 gi) {
|
||||
auto order = array_make<i32>(heap_allocator(), 0, tasks.count);
|
||||
defer (array_free(&order));
|
||||
{
|
||||
struct Frame {
|
||||
i32 k;
|
||||
isize next;
|
||||
};
|
||||
auto frames = array_make<Frame>(heap_allocator(), 0, tasks.count);
|
||||
auto frames = array_make<EscapeOrderFrame>(heap_allocator(), 0, tasks.count);
|
||||
auto visited = array_make<bool>(heap_allocator(), tasks.count);
|
||||
defer (array_free(&frames));
|
||||
defer (array_free(&visited));
|
||||
for (bool &v : visited) {
|
||||
v = false;
|
||||
}
|
||||
for_array(root, tasks) {
|
||||
if (visited[root]) {
|
||||
continue;
|
||||
}
|
||||
visited[root] = true;
|
||||
array_add(&frames, Frame{cast(i32)root, 0});
|
||||
array_add(&frames, EscapeOrderFrame{cast(i32)root, 0});
|
||||
while (frames.count > 0) {
|
||||
Frame &top = frames[frames.count-1];
|
||||
EscapeOrderFrame &top = frames[frames.count-1];
|
||||
Array<i32> const &callees = tasks[top.k].callees;
|
||||
if (top.next < callees.count) {
|
||||
i32 callee = callees[top.next++];
|
||||
if (!visited[callee]) {
|
||||
visited[callee] = true;
|
||||
array_add(&frames, Frame{callee, 0});
|
||||
array_add(&frames, EscapeOrderFrame{callee, 0});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -3662,19 +3629,6 @@ gb_internal void escape_analyse_group(EscapeGraph *g, i32 gi) {
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal Slice<EscapeFlow> escape_flows_of(EscapeAnalysis *ea, Entity *e) {
|
||||
DeclInfo *d = e->decl_info;
|
||||
if (d == nullptr || d->proc_info == nullptr) {
|
||||
// foreign, or its body was not checked
|
||||
return {};
|
||||
}
|
||||
if (!d->escapes_analysed.load() && ea->graph->group_of[d->escape_index] != ea->group) {
|
||||
// not a call the checker recorded, which should not happen
|
||||
return {};
|
||||
}
|
||||
return d->escape_flows;
|
||||
}
|
||||
|
||||
gb_internal bool escape_call_edge(CheckedCall const &call, i32 *caller, i32 *callee) {
|
||||
DeclInfo *d = call.callee->decl_info;
|
||||
if (call.caller->proc_info == nullptr || d == nullptr || d->proc_info == nullptr) {
|
||||
@@ -3766,12 +3720,9 @@ gb_internal void escape_for_calls(EscapeGraph *g, PerThreadArray<CheckedCall> *c
|
||||
}
|
||||
|
||||
gb_internal WORKER_TASK_PROC(escape_graph_find_callers) {
|
||||
EscapeGraph *g = &escape_graph;
|
||||
EscapeGraph *g = cast(EscapeGraph *)data;
|
||||
i32 count = cast(i32)g->procs.count;
|
||||
array_init(&g->caller_offsets, heap_allocator(), count+1);
|
||||
for (i32 &offset : g->caller_offsets) {
|
||||
offset = 0;
|
||||
}
|
||||
for (i32 callee : g->targets) {
|
||||
g->caller_offsets[callee+1] += 1;
|
||||
}
|
||||
@@ -3833,10 +3784,9 @@ gb_internal void check_escapes(Checker *c) {
|
||||
array_free(&g->group_of);
|
||||
array_free(&g->group_offsets);
|
||||
array_free(&g->members);
|
||||
gb_free(heap_allocator(), g->pending);
|
||||
gb_free(heap_allocator(), g->cursors);
|
||||
g->pending = nullptr;
|
||||
g->cursors = nullptr;
|
||||
slice_free(&g->pending, heap_allocator());
|
||||
slice_free(&g->cursors, heap_allocator());
|
||||
*g = {};
|
||||
});
|
||||
array_init(&g->procs, heap_allocator());
|
||||
per_thread_array_gather(&c->info.checked_bodies_queue, &g->procs);
|
||||
@@ -3848,13 +3798,9 @@ gb_internal void check_escapes(Checker *c) {
|
||||
escape_for_procs(g, escape_graph_number_procs);
|
||||
|
||||
// the calls of each procedure are counted, and then placed after those of the procedures before it
|
||||
g->cursors = gb_alloc_array(heap_allocator(), std::atomic<i32>, count);
|
||||
for (i32 v = 0; v < count; v++) {
|
||||
g->cursors[v].store(0, std::memory_order_relaxed);
|
||||
}
|
||||
g->cursors = slice_make<std::atomic<i32> >(heap_allocator(), count);
|
||||
escape_for_calls(g, &c->info.checked_calls_queue, escape_graph_count_calls);
|
||||
array_init(&g->offsets, heap_allocator(), count+1);
|
||||
g->offsets[0] = 0;
|
||||
for (i32 v = 0; v < count; v++) {
|
||||
g->offsets[v+1] = g->offsets[v] + g->cursors[v].load(std::memory_order_relaxed);
|
||||
g->cursors[v].store(g->offsets[v], std::memory_order_relaxed);
|
||||
@@ -3868,7 +3814,7 @@ gb_internal void check_escapes(Checker *c) {
|
||||
// only needed to release the groups when threaded, and found meanwhile
|
||||
TaskGroup callers = {};
|
||||
if (g->threaded) {
|
||||
thread_pool_add_task(&callers, escape_graph_find_callers, nullptr);
|
||||
thread_pool_add_task(&callers, escape_graph_find_callers, g);
|
||||
}
|
||||
|
||||
array_init(&g->group_of, heap_allocator(), count);
|
||||
@@ -3890,10 +3836,7 @@ gb_internal void check_escapes(Checker *c) {
|
||||
}
|
||||
|
||||
thread_pool_wait(&callers);
|
||||
g->pending = gb_alloc_array(heap_allocator(), std::atomic<i32>, group_count);
|
||||
for (i32 gi = 0; gi < group_count; gi++) {
|
||||
g->pending[gi].store(0, std::memory_order_relaxed);
|
||||
}
|
||||
g->pending = slice_make<std::atomic<i32> >(heap_allocator(), group_count);
|
||||
escape_for_procs(g, escape_graph_count_pending);
|
||||
|
||||
// all found before any is analysed, as analysing one releases others
|
||||
|
||||
+1
-1
@@ -228,7 +228,7 @@ struct DeclInfo {
|
||||
|
||||
i32 escape_index; // of its body, see `EscapeGraph`
|
||||
std::atomic<bool> escapes_analysed;
|
||||
Slice<struct EscapeFlow> escape_flows; // see `escape_flows_of`
|
||||
Slice<struct EscapeFlow> escape_flows; // see `escape_call`
|
||||
|
||||
bool is_using;
|
||||
bool foreign_require_results;
|
||||
|
||||
Reference in new issue
Block a user