mirror of
https://github.com/odin-lang/Odin.git
synced 2026-11-01 08:19:34 -05:00
2912 lines
84 KiB
C++
2912 lines
84 KiB
C++
// Whatever is unknown, e.g. the result of a call through a procedure value, is assumed to not point into the stack frame
|
|
// An explicit conversion to 'rawptr' or 'uintptr', or a 'transmute', opts out
|
|
// A call uses a summary of what of its arguments may reach its results, analysing the callee first if it must
|
|
|
|
enum EscapeStepKind : u8 {
|
|
EscapeStep_Invalid,
|
|
EscapeStep_Field,
|
|
EscapeStep_AnyElement,
|
|
EscapeStep_Deref, // only in the location of an outer store
|
|
};
|
|
|
|
struct EscapeStep {
|
|
EscapeStepKind kind;
|
|
Entity * field;
|
|
};
|
|
|
|
// immutable once made, so paths share their steps
|
|
typedef Slice<EscapeStep> EscapePath;
|
|
|
|
struct EscapeObject {
|
|
Entity *entity;
|
|
Ast * temp;
|
|
};
|
|
|
|
enum EscapeOriginKind : u8 {
|
|
EscapeOrigin_Invalid,
|
|
EscapeOrigin_Outer,
|
|
EscapeOrigin_Local,
|
|
EscapeOrigin_Temp,
|
|
EscapeOrigin_Owned, // the buffer of a local dynamic array or map, tracked as its contents
|
|
EscapeOrigin_Variadic,
|
|
EscapeOrigin_Param, // what the pointers a parameter holds at its path point to, which is the caller's
|
|
EscapeOrigin_ParamLoad, // what the pointers loaded once through them point to
|
|
EscapeOrigin_ParamDeep, // what is reached by loading through them more than once
|
|
};
|
|
|
|
// where the buffer of a dynamic array or map is loaded through it
|
|
enum EscapeFlowKind : u8 {
|
|
EscapeFlow_Invalid,
|
|
EscapeFlow_Value, // the pointers of the argument, or offsets of them
|
|
EscapeFlow_Load, // the pointers loaded once through them
|
|
EscapeFlow_Deep, // the pointers loaded more than once through them
|
|
};
|
|
|
|
enum EscapeFlowTargetKind : u8 {
|
|
EscapeFlowTarget_Invalid,
|
|
EscapeFlowTarget_Result,
|
|
EscapeFlowTarget_Pointee, // what an argument points to
|
|
EscapeFlowTarget_Loaded, // what a pointer loaded once through an argument points to
|
|
EscapeFlowTarget_Deep, // what is reached by loading through an argument more than once
|
|
EscapeFlowTarget_Outer, // e.g. a global
|
|
};
|
|
|
|
// what an argument holds at a path may reach a result of the call, or be stored where it outlives the call
|
|
struct EscapeFlow {
|
|
EscapeFlowKind kind;
|
|
isize param;
|
|
EscapePath param_path;
|
|
EscapeFlowTargetKind target;
|
|
isize target_index; // of the result, or of the argument it is stored through
|
|
EscapePath target_path; // within the result, or of the pointer within that argument
|
|
};
|
|
|
|
struct EscapeOrigin {
|
|
EscapeOriginKind kind;
|
|
EscapeObject obj;
|
|
EscapePath path;
|
|
};
|
|
|
|
struct EscapeValueFact {
|
|
EscapePath path;
|
|
EscapeOrigin origin;
|
|
bool definite; // straight from the expression itself, not read back from a variable or through a pointer
|
|
};
|
|
typedef Array<EscapeValueFact> EscapeValue;
|
|
|
|
struct EscapeFact {
|
|
EscapeObject obj;
|
|
EscapePath path;
|
|
EscapeOrigin origin;
|
|
};
|
|
|
|
struct EscapeOuterStore {
|
|
Entity * root;
|
|
EscapePath path;
|
|
bool exact;
|
|
Ast * node; // the left hand side, or a call storing into memory it was given, unless what is stored is the caller's
|
|
EscapeOrigin dest; // what it is stored through
|
|
EscapeOrigin origin; // of what is stored
|
|
};
|
|
|
|
struct EscapeState {
|
|
bool reachable;
|
|
Array<EscapeFact> facts;
|
|
Array<EscapeOuterStore> outers;
|
|
};
|
|
|
|
enum EscapeTargetKind : u8 {
|
|
EscapeTarget_Invalid,
|
|
EscapeTarget_Labelled, // a block or if statement, which only a labelled break exits
|
|
EscapeTarget_Loop,
|
|
EscapeTarget_Switch,
|
|
};
|
|
|
|
struct EscapeTarget {
|
|
EscapeTargetKind kind;
|
|
Ast * stmt;
|
|
isize defer_depth;
|
|
EscapeState breaks;
|
|
EscapeState continues;
|
|
};
|
|
|
|
enum EscapeUpdateKind : u8 {
|
|
EscapeUpdate_Invalid,
|
|
EscapeUpdate_Replace,
|
|
EscapeUpdate_Add,
|
|
};
|
|
|
|
enum EscapeReportKind : u8 {
|
|
EscapeReport_Invalid,
|
|
EscapeReport_Return, // definitely, so an error
|
|
EscapeReport_MayReturn,
|
|
EscapeReport_Store,
|
|
EscapeReport_CallStore,
|
|
};
|
|
|
|
struct EscapeReport {
|
|
EscapeReportKind kind;
|
|
Ast * node;
|
|
String expr_str;
|
|
EscapeOrigin origin;
|
|
};
|
|
|
|
struct EscapeAlias {
|
|
Entity * e;
|
|
EscapeOrigin origin;
|
|
};
|
|
|
|
struct EscapeAnalysis {
|
|
TypeProc * pt;
|
|
Entity * variadic;
|
|
EscapeState state;
|
|
EscapeState fallthrough;
|
|
Array<Array<Ast *> > defers;
|
|
Array<EscapeTarget> targets;
|
|
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
|
|
};
|
|
|
|
|
|
gb_internal bool escape_path_has_prefix(EscapePath const &p, EscapePath const &prefix) {
|
|
if (prefix.count > p.count) {
|
|
return false;
|
|
}
|
|
for_array(i, prefix) {
|
|
if (p[i].kind != prefix[i].kind ||
|
|
p[i].field != prefix[i].field) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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};
|
|
return p;
|
|
}
|
|
|
|
gb_internal EscapePath escape_path_concat(EscapePath const &p, EscapePath const &q) {
|
|
if (q.count == 0) {
|
|
return p;
|
|
}
|
|
if (p.count == 0) {
|
|
return q;
|
|
}
|
|
auto r = slice_make<EscapeStep>(temporary_allocator(), p.count+q.count);
|
|
slice_copy(&r, p);
|
|
slice_copy(&r, q, p.count);
|
|
return r;
|
|
}
|
|
|
|
gb_internal bool escape_object_eq(EscapeObject const &a, EscapeObject const &b) {
|
|
return a.entity == b.entity &&
|
|
a.temp == b.temp;
|
|
}
|
|
|
|
gb_internal bool escape_origin_eq(EscapeOrigin const &a, EscapeOrigin const &b) {
|
|
return a.kind == b.kind &&
|
|
escape_object_eq(a.obj, b.obj) &&
|
|
escape_path_eq(a.path, b.path);
|
|
}
|
|
|
|
gb_internal bool escape_outer_eq(EscapeOuterStore const &a, EscapeOuterStore const &b) {
|
|
return a.root == b.root &&
|
|
escape_path_eq(a.path, b.path) &&
|
|
a.exact == b.exact &&
|
|
a.node == b.node &&
|
|
escape_origin_eq(a.dest, b.dest) &&
|
|
escape_origin_eq(a.origin, b.origin);
|
|
}
|
|
|
|
gb_internal EscapeValue escape_value(void) {
|
|
// NOTE(bill): most values are empty so allocate on the fly when needed
|
|
return array_make<EscapeValueFact>(temporary_allocator(), 0, 0);
|
|
}
|
|
|
|
gb_internal void escape_value_add(EscapeValue *v, EscapePath const &path, EscapeOrigin const &o, bool definite=false) {
|
|
for (EscapeValueFact &f : *v) {
|
|
if (escape_path_eq(f.path, path) && escape_origin_eq(f.origin, o)) {
|
|
f.definite |= definite;
|
|
return;
|
|
}
|
|
}
|
|
array_add(v, EscapeValueFact{path, o, definite});
|
|
}
|
|
|
|
// either of two values, so neither is definite
|
|
gb_internal void escape_value_merge(EscapeValue *v, EscapeValue const &w) {
|
|
for (EscapeValueFact &f : *v) {
|
|
f.definite = false;
|
|
}
|
|
for (EscapeValueFact const &f : w) {
|
|
escape_value_add(v, f.path, f.origin);
|
|
}
|
|
}
|
|
|
|
gb_internal EscapeValue escape_value_of(EscapeOriginKind kind, EscapeObject const &obj, EscapePath const &path) {
|
|
EscapeValue v = escape_value();
|
|
// the stack memory is made by the expression itself
|
|
bool definite = kind == EscapeOrigin_Local || kind == EscapeOrigin_Temp;
|
|
escape_value_add(&v, {}, EscapeOrigin{kind, obj, path}, definite);
|
|
return v;
|
|
}
|
|
|
|
gb_internal isize escape_path_deref_index(EscapePath const &p) {
|
|
for_array(i, p) {
|
|
if (p[i].kind == EscapeStep_Deref) {
|
|
return i;
|
|
}
|
|
}
|
|
return p.count;
|
|
}
|
|
|
|
|
|
gb_internal EscapeValue escape_pointer_offset(EscapeValue const &v, EscapePath const &extra) {
|
|
EscapeValue w = escape_value();
|
|
for (EscapeValueFact const &f : v) {
|
|
EscapeOrigin o = f.origin;
|
|
switch (o.kind) {
|
|
case EscapeOrigin_Local:
|
|
case EscapeOrigin_Temp:
|
|
case EscapeOrigin_Owned:
|
|
o.path = escape_path_concat(o.path, extra);
|
|
break;
|
|
case EscapeOrigin_Param:
|
|
if (extra.count > 0 && escape_path_deref_index(o.path) == o.path.count) {
|
|
o.path = escape_path_concat(o.path, escape_path_of(EscapeStep_Deref));
|
|
}
|
|
o.path = escape_path_concat(o.path, extra);
|
|
break;
|
|
}
|
|
escape_value_add(&w, {}, o, f.definite);
|
|
}
|
|
return w;
|
|
}
|
|
|
|
gb_internal EscapeValue escape_as_pointer(EscapeValue const &v) {
|
|
return escape_pointer_offset(v, {});
|
|
}
|
|
|
|
// what a parameter holds at a path within what it holds at another
|
|
gb_internal EscapeOrigin escape_origin_within(EscapeOrigin o, EscapePath const &path, isize from) {
|
|
if (o.kind == EscapeOrigin_Param && escape_path_deref_index(o.path) == o.path.count) {
|
|
o.path = escape_path_concat(o.path, slice(path, from, path.count));
|
|
}
|
|
return o;
|
|
}
|
|
|
|
gb_internal void escape_project_into(EscapeValue *out, EscapeValueFact const &f, EscapePath const &path) {
|
|
if (escape_path_has_prefix(f.path, path)) {
|
|
escape_value_add(out, slice(f.path, path.count, f.path.count), f.origin, f.definite);
|
|
} else if (escape_path_has_prefix(path, f.path)) {
|
|
escape_value_add(out, {}, escape_origin_within(f.origin, path, f.path.count), f.definite);
|
|
}
|
|
}
|
|
|
|
gb_internal EscapeValue escape_value_project(EscapeValue const &v, EscapePath const &path) {
|
|
EscapeValue w = escape_value();
|
|
for (EscapeValueFact const &f : v) {
|
|
escape_project_into(&w, f, path);
|
|
}
|
|
return w;
|
|
}
|
|
|
|
gb_internal EscapeState escape_state_unreachable(void) {
|
|
EscapeState s = {};
|
|
s.facts = array_make<EscapeFact>(temporary_allocator(), 0, 0);
|
|
s.outers = array_make<EscapeOuterStore>(temporary_allocator(), 0, 0);
|
|
return s;
|
|
}
|
|
|
|
gb_internal EscapeState escape_state_clone(EscapeState const &s) {
|
|
EscapeState c = {};
|
|
c.reachable = s.reachable;
|
|
c.facts = array_clone(temporary_allocator(), s.facts);
|
|
c.outers = array_clone(temporary_allocator(), s.outers);
|
|
return c;
|
|
}
|
|
|
|
gb_internal void escape_add_fact(Array<EscapeFact> *facts, EscapeFact const &f) {
|
|
for (EscapeFact const &g : *facts) {
|
|
if (escape_object_eq(f.obj, g.obj) &&
|
|
escape_path_eq(f.path, g.path) &&
|
|
escape_origin_eq(f.origin, g.origin)) {
|
|
return;
|
|
}
|
|
}
|
|
array_add(facts, f);
|
|
}
|
|
|
|
gb_internal bool escape_state_join(EscapeState *dst, EscapeState const &src) {
|
|
if (!src.reachable) {
|
|
return false;
|
|
}
|
|
|
|
if (!dst->reachable) {
|
|
*dst = escape_state_clone(src);
|
|
return true;
|
|
}
|
|
|
|
bool changed = false;
|
|
for (EscapeFact const &f : src.facts) {
|
|
isize count = dst->facts.count;
|
|
escape_add_fact(&dst->facts, f);
|
|
changed |= dst->facts.count != count;
|
|
}
|
|
|
|
for (EscapeOuterStore const &o : src.outers) {
|
|
bool found = false;
|
|
for (EscapeOuterStore const &p : dst->outers) {
|
|
if (escape_outer_eq(o, p)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
array_add(&dst->outers, o);
|
|
changed = true;
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
|
|
gb_internal bool escape_type_has_pointers(Type *t) {
|
|
t = base_type(t);
|
|
if (t == nullptr) {
|
|
return false;
|
|
}
|
|
switch (t->kind) {
|
|
case Type_Basic:
|
|
switch (t->Basic.kind) {
|
|
case Basic_rawptr:
|
|
case Basic_string:
|
|
case Basic_cstring:
|
|
case Basic_string16:
|
|
case Basic_cstring16:
|
|
case Basic_any:
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case Type_Pointer:
|
|
case Type_MultiPointer:
|
|
case Type_Slice:
|
|
case Type_DynamicArray:
|
|
case Type_Map:
|
|
case Type_SoaPointer:
|
|
return true;
|
|
|
|
case Type_Array: return escape_type_has_pointers(t->Array.elem);
|
|
case Type_EnumeratedArray: return escape_type_has_pointers(t->EnumeratedArray.elem);
|
|
case Type_FixedCapacityDynamicArray: return escape_type_has_pointers(t->FixedCapacityDynamicArray.elem);
|
|
case Type_SimdVector: return escape_type_has_pointers(t->SimdVector.elem);
|
|
|
|
case Type_Struct:
|
|
for (Entity *f : t->Struct.fields) {
|
|
if (escape_type_has_pointers(f->type)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
case Type_Union:
|
|
for (Type *v : t->Union.variants) {
|
|
if (escape_type_has_pointers(v)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
case Type_Tuple:
|
|
for (Entity *v : t->Tuple.variables) {
|
|
if (escape_type_has_pointers(v->type)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
gb_internal bool escape_type_is_plain(Type *t) {
|
|
t = base_type(t);
|
|
if (t == nullptr) {
|
|
return true;
|
|
}
|
|
switch (t->kind) {
|
|
case Type_Basic:
|
|
case Type_SimdVector:
|
|
return !escape_type_has_pointers(t);
|
|
|
|
case Type_Enum:
|
|
case Type_BitSet:
|
|
case Type_BitField:
|
|
case Type_Matrix:
|
|
case Type_Proc:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
gb_internal bool escape_is_local(Entity *e) {
|
|
return e != nullptr &&
|
|
e->kind == Entity_Variable &&
|
|
!e->Variable.is_global &&
|
|
!e->Variable.is_foreign &&
|
|
(e->flags & EntityFlag_Static) == 0;
|
|
}
|
|
|
|
gb_internal bool escape_is_param(Entity *e) {
|
|
return (e->flags & (EntityFlag_Param|EntityFlag_Result)) == EntityFlag_Param;
|
|
}
|
|
|
|
gb_internal bool escape_is_reference(Entity *e) {
|
|
return (e->flags & (EntityFlag_ForValue|EntityFlag_SwitchValue)) != 0 &&
|
|
(e->flags & EntityFlag_Value) == 0;
|
|
}
|
|
|
|
gb_internal bool escape_is_using_field(Entity *e) {
|
|
return e->using_parent != nullptr &&
|
|
e->using_expr == nullptr;
|
|
}
|
|
|
|
|
|
gb_internal EscapeValue escape_obj_value(EscapeAnalysis *ea, EscapeObject const &obj, EscapePath const &path) {
|
|
EscapeValue v = escape_value();
|
|
for (EscapeFact const &f : ea->state.facts) {
|
|
if (escape_object_eq(f.obj, obj)) {
|
|
escape_project_into(&v, EscapeValueFact{f.path, f.origin}, path);
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
// the type of what is at a path within a value of a type, if known, e.g. not when the path was made through a cast
|
|
gb_internal Type *escape_type_within(Type *t, EscapePath const &path) {
|
|
for (EscapeStep const &step : path) {
|
|
Type *bt = base_type(t);
|
|
if (bt == nullptr) {
|
|
return nullptr;
|
|
}
|
|
if (step.kind == EscapeStep_Field) {
|
|
if (bt->kind != Type_Struct) {
|
|
return nullptr;
|
|
}
|
|
bool is_own_field = false;
|
|
for (Entity *field : bt->Struct.fields) {
|
|
is_own_field |= field == step.field;
|
|
}
|
|
if (!is_own_field) {
|
|
return nullptr;
|
|
}
|
|
t = step.field->type;
|
|
continue;
|
|
}
|
|
switch (bt->kind) {
|
|
case Type_Array: t = bt->Array.elem; break;
|
|
case Type_EnumeratedArray: t = bt->EnumeratedArray.elem; break;
|
|
case Type_FixedCapacityDynamicArray: t = bt->FixedCapacityDynamicArray.elem; break;
|
|
case Type_Slice: t = bt->Slice.elem; break;
|
|
case Type_DynamicArray: t = bt->DynamicArray.elem; break;
|
|
case Type_Map: t = bt->Map.value; break;
|
|
case Type_Matrix: t = bt->Matrix.elem; break;
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
|
|
gb_internal Type *escape_type_at(EscapeObject const &obj, EscapePath const &path) {
|
|
if (obj.entity != nullptr) {
|
|
return escape_type_within(obj.entity->type, path);
|
|
}
|
|
if (obj.temp != nullptr) {
|
|
return escape_type_within(obj.temp->tav.type, path);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// what a value of the type points to, e.g. the elements of a slice
|
|
gb_internal Type *escape_pointee_type(Type *t) {
|
|
t = base_type(t);
|
|
if (t == nullptr) {
|
|
return nullptr;
|
|
}
|
|
switch (t->kind) {
|
|
case Type_Pointer: return t->Pointer.elem;
|
|
case Type_MultiPointer: return t->MultiPointer.elem;
|
|
case Type_Slice: return t->Slice.elem;
|
|
case Type_DynamicArray: return t->DynamicArray.elem;
|
|
case Type_Basic:
|
|
switch (t->Basic.kind) {
|
|
case Basic_string:
|
|
case Basic_cstring:
|
|
case Basic_string16:
|
|
case Basic_cstring16:
|
|
return t_u8;
|
|
}
|
|
return nullptr;
|
|
}
|
|
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)) {
|
|
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--) {
|
|
EscapeFact const &f = ea->state.facts[i];
|
|
if (!escape_object_eq(f.obj, obj)) {
|
|
continue;
|
|
}
|
|
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)) {
|
|
array_unordered_remove(&ea->state.facts, i);
|
|
}
|
|
}
|
|
for (EscapeFact const &p : pieces) {
|
|
escape_add_fact(&ea->state.facts, p);
|
|
}
|
|
}
|
|
for (EscapeValueFact const &vf : v) {
|
|
escape_add_fact(&ea->state.facts, EscapeFact{obj, escape_path_concat(path, vf.path), vf.origin});
|
|
}
|
|
}
|
|
|
|
// what loading through a pointer of the caller's memory gives
|
|
gb_internal EscapeOrigin escape_param_loaded(EscapeOrigin o) {
|
|
switch (o.kind) {
|
|
case EscapeOrigin_Param: o.kind = EscapeOrigin_ParamLoad; break;
|
|
case EscapeOrigin_ParamLoad: o.kind = EscapeOrigin_ParamDeep; break;
|
|
}
|
|
return o;
|
|
}
|
|
|
|
gb_internal EscapeValue escape_load(EscapeAnalysis *ea, EscapeValue const &ptr, Type *type) {
|
|
EscapeValue v = escape_value();
|
|
if (type != nullptr && !escape_type_has_pointers(type)) {
|
|
return v;
|
|
}
|
|
for (EscapeValueFact const &pf : ptr) {
|
|
EscapeOrigin const &o = pf.origin;
|
|
switch (o.kind) {
|
|
case EscapeOrigin_Outer:
|
|
escape_value_add(&v, {}, o);
|
|
break;
|
|
|
|
case EscapeOrigin_Variadic: {
|
|
// NOTE(bill): its 'any' elements are boxed in the caller's stack frame too
|
|
Type *elem = base_type(o.obj.entity->type);
|
|
if (elem->kind == Type_Slice && is_type_any(elem->Slice.elem)) {
|
|
escape_value_add(&v, {}, o);
|
|
} else {
|
|
// its elements are the caller's arguments
|
|
escape_value_add(&v, {}, EscapeOrigin{EscapeOrigin_ParamLoad, o.obj, o.path});
|
|
}
|
|
break;
|
|
}
|
|
case EscapeOrigin_Local:
|
|
if (escape_is_param(o.obj.entity)) {
|
|
// what a parameter holds is the caller's
|
|
escape_value_add(&v, {}, EscapeOrigin{EscapeOrigin_Param, o.obj, o.path});
|
|
break;
|
|
}
|
|
/*fallthrough*/
|
|
case EscapeOrigin_Temp:
|
|
case EscapeOrigin_Owned:
|
|
for (EscapeFact const &f : ea->state.facts) {
|
|
if (escape_object_eq(f.obj, o.obj)) {
|
|
escape_project_into(&v, EscapeValueFact{f.path, f.origin}, o.path);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EscapeOrigin_Param:
|
|
case EscapeOrigin_ParamLoad:
|
|
case EscapeOrigin_ParamDeep:
|
|
escape_value_add(&v, {}, escape_param_loaded(o));
|
|
break;
|
|
|
|
case EscapeOrigin_Invalid:
|
|
break;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
if ((expr->viral_state_flags & (ViralStateFlag_ContainsOrReturn|ViralStateFlag_ContainsOrBreak)) != 0) {
|
|
escape_expr(ea, expr);
|
|
return;
|
|
}
|
|
if (Ast *call = escape_call_of(expr)) {
|
|
escape_call(ea, call);
|
|
}
|
|
}
|
|
|
|
gb_internal void escape_forget_entity(EscapeAnalysis *ea, Entity *e) {
|
|
if (e == nullptr) {
|
|
return;
|
|
}
|
|
for (isize i = ea->state.facts.count-1; i >= 0; i--) {
|
|
if (ea->state.facts[i].obj.entity == e) {
|
|
array_unordered_remove(&ea->state.facts, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
gb_internal void escape_forget(EscapeAnalysis *ea, Ast *decl) {
|
|
if (decl != nullptr && decl->kind == Ast_ValueDecl && decl->ValueDecl.is_mutable) {
|
|
for (Ast *name : decl->ValueDecl.names) {
|
|
escape_forget_entity(ea, entity_of_node(name));
|
|
}
|
|
}
|
|
}
|
|
|
|
gb_internal EscapeValue escape_elems_at(EscapeAnalysis *ea, EscapeValue const &addr, Type *t) {
|
|
EscapeValue ptr = escape_value();
|
|
t = base_type(t);
|
|
if (t == nullptr) {
|
|
return ptr;
|
|
}
|
|
switch (t->kind) {
|
|
case Type_Array:
|
|
case Type_EnumeratedArray:
|
|
case Type_FixedCapacityDynamicArray:
|
|
case Type_Matrix:
|
|
return escape_pointer_offset(addr, escape_path_of(EscapeStep_AnyElement));
|
|
case Type_Slice:
|
|
case Type_MultiPointer:
|
|
case Type_Basic:
|
|
return escape_as_pointer(escape_load(ea, addr, t));
|
|
case Type_DynamicArray:
|
|
case Type_Map:
|
|
for (EscapeValueFact const &f : addr) {
|
|
EscapeOrigin o = f.origin;
|
|
switch (o.kind) {
|
|
case EscapeOrigin_Local:
|
|
if (escape_is_param(o.obj.entity)) {
|
|
// the buffer of a parameter is the caller's
|
|
escape_value_add(&ptr, {}, EscapeOrigin{EscapeOrigin_Param, o.obj, o.path});
|
|
break;
|
|
}
|
|
/*fallthrough*/
|
|
case EscapeOrigin_Temp:
|
|
case EscapeOrigin_Owned:
|
|
o.kind = EscapeOrigin_Owned;
|
|
o.path = escape_path_concat(o.path, escape_path_of(EscapeStep_AnyElement));
|
|
escape_value_add(&ptr, {}, o, f.definite);
|
|
break;
|
|
case EscapeOrigin_Outer:
|
|
escape_value_add(&ptr, {}, o);
|
|
break;
|
|
case EscapeOrigin_Param:
|
|
case EscapeOrigin_ParamLoad:
|
|
case EscapeOrigin_ParamDeep:
|
|
escape_value_add(&ptr, {}, escape_param_loaded(o));
|
|
break;
|
|
}
|
|
}
|
|
return ptr;
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
gb_internal EscapeValue escape_elems_of(EscapeAnalysis *ea, Ast *x) {
|
|
Type *t = base_type(x->tav.type);
|
|
if (t == nullptr) {
|
|
escape_expr(ea, x);
|
|
return escape_value();
|
|
}
|
|
switch (t->kind) {
|
|
case Type_Pointer:
|
|
return escape_elems_at(ea, escape_as_pointer(escape_expr(ea, x)), t->Pointer.elem);
|
|
|
|
case Type_Slice:
|
|
case Type_MultiPointer:
|
|
case Type_Basic:
|
|
return escape_as_pointer(escape_expr(ea, 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) {
|
|
return false;
|
|
}
|
|
|
|
Entity *f = entity_of_node(se->selector);
|
|
if (f == nullptr || f->kind != Entity_Variable) {
|
|
return false;
|
|
}
|
|
|
|
// the fields of a raw union share its storage
|
|
*path = {};
|
|
if (!is_type_raw_union(type_deref(se->expr->tav.type))) {
|
|
*path = escape_path_of(EscapeStep_Field, f);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
gb_internal Entity *escape_package_selector(Ast *se_node) {
|
|
ast_node(se, SelectorExpr, se_node);
|
|
Entity *pkg = entity_of_node(se->expr);
|
|
if (pkg == nullptr || pkg->kind != Entity_ImportName) {
|
|
return nullptr;
|
|
}
|
|
return entity_of_node(se->selector);
|
|
}
|
|
|
|
|
|
gb_internal EscapeValue escape_convert(EscapeAnalysis *ea, EscapeValue const &v, Ast *expr, Type *type) {
|
|
if (type == nullptr || expr == nullptr || !is_type_any(type)) {
|
|
return v;
|
|
}
|
|
|
|
Type *src = expr->tav.type;
|
|
if (src == nullptr || is_type_any(src) || is_type_untyped_nil(src) || is_type_untyped_uninit(src)) {
|
|
return v;
|
|
}
|
|
|
|
// the backend boxes an addressable value by its address, and anything else in a stack temporary
|
|
if (expr->tav.mode == Addressing_Variable) {
|
|
return escape_addr(ea, expr);
|
|
}
|
|
escape_store_obj(ea, {nullptr, expr}, {}, v, EscapeUpdate_Replace);
|
|
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();
|
|
}
|
|
|
|
if (escape_is_reference(e)) {
|
|
EscapeValue ptr = escape_value();
|
|
for (EscapeAlias const &a : ea->aliases) {
|
|
if (a.e == e) {
|
|
escape_value_add(&ptr, {}, a.origin);
|
|
}
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
if ((e->flags & (EntityFlag_ForValue|EntityFlag_SwitchValue)) != 0) {
|
|
return escape_value();
|
|
}
|
|
|
|
if (escape_is_using_field(e)) {
|
|
Entity *parent = e->using_parent;
|
|
Type *t = base_type(type_deref(parent->type));
|
|
Entity *field = nullptr;
|
|
if (t != nullptr && t->kind == Type_Struct) {
|
|
for (Entity *f : t->Struct.fields) {
|
|
if (f->token.string == e->token.string) {
|
|
field = f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (field == nullptr) {
|
|
return escape_value();
|
|
}
|
|
|
|
EscapeValue base = {};
|
|
if (is_type_pointer(parent->type)) {
|
|
base = escape_ident_value(ea, parent);
|
|
} else {
|
|
base = escape_ident_addr(ea, parent);
|
|
}
|
|
return escape_pointer_offset(base, escape_path_of(EscapeStep_Field, field));
|
|
}
|
|
|
|
if (!escape_is_local(e)) {
|
|
return escape_value_of(EscapeOrigin_Outer, {}, {});
|
|
}
|
|
return escape_value_of(EscapeOrigin_Local, {e}, {});
|
|
}
|
|
|
|
gb_internal EscapeValue escape_ident_value(EscapeAnalysis *ea, Entity *e) {
|
|
if (e == nullptr || e->kind != Entity_Variable) {
|
|
return escape_value();
|
|
}
|
|
|
|
if (escape_is_reference(e) || escape_is_using_field(e)) {
|
|
return escape_load(ea, escape_ident_addr(ea, e), e->type);
|
|
}
|
|
|
|
if (!escape_is_local(e)) {
|
|
if (escape_type_has_pointers(e->type)) {
|
|
return escape_value_of(EscapeOrigin_Outer, {}, {});
|
|
}
|
|
return escape_value();
|
|
}
|
|
|
|
if (escape_is_param(e)) {
|
|
// a parameter cannot be assigned to
|
|
if (escape_type_is_plain(e->type)) {
|
|
return escape_value();
|
|
}
|
|
if (e == ea->variadic) {
|
|
return escape_value_of(EscapeOrigin_Variadic, {e}, {});
|
|
}
|
|
return escape_value_of(EscapeOrigin_Param, {e}, {});
|
|
}
|
|
return escape_obj_value(ea, {e}, {});
|
|
}
|
|
|
|
gb_internal EscapeValue escape_variant_addr(EscapeAnalysis *ea, Ast *x) {
|
|
Type *t = x->tav.type;
|
|
if (is_type_any(t) || is_type_pointer(t)) {
|
|
return escape_as_pointer(escape_expr(ea, x));
|
|
}
|
|
return escape_addr(ea, x);
|
|
}
|
|
|
|
gb_internal EscapeValue escape_addr(EscapeAnalysis *ea, Ast *expr) {
|
|
if (expr == nullptr) {
|
|
return escape_value();
|
|
}
|
|
|
|
switch (expr->kind) {
|
|
case_ast_node(pe, ParenExpr, expr);
|
|
return escape_addr(ea, pe->expr);
|
|
case_end;
|
|
|
|
case_ast_node(i, Ident, expr);
|
|
return escape_ident_addr(ea, entity_of_node(expr));
|
|
case_end;
|
|
|
|
case_ast_node(se, SelectorExpr, expr);
|
|
if (Entity *g = escape_package_selector(expr)) {
|
|
return escape_ident_addr(ea, g);
|
|
}
|
|
EscapePath path = {};
|
|
if (!escape_selector_path(expr, &path)) {
|
|
escape_visit_exits(ea, se->expr);
|
|
return escape_value();
|
|
}
|
|
if (is_type_pointer(se->expr->tav.type)) {
|
|
return escape_pointer_offset(escape_expr(ea, se->expr), path);
|
|
}
|
|
return escape_pointer_offset(escape_addr(ea, se->expr), path);
|
|
case_end;
|
|
|
|
case_ast_node(ie, IndexExpr, expr);
|
|
escape_visit_exits(ea, ie->index);
|
|
return escape_elems_of(ea, ie->expr);
|
|
case_end;
|
|
|
|
case_ast_node(de, DerefExpr, expr);
|
|
return escape_as_pointer(escape_expr(ea, de->expr));
|
|
case_end;
|
|
|
|
case_ast_node(cl, CompoundLit, expr);
|
|
EscapeValue v = escape_expr(ea, expr);
|
|
escape_store_obj(ea, {nullptr, expr}, {}, v, EscapeUpdate_Replace);
|
|
return escape_value_of(EscapeOrigin_Temp, {nullptr, expr}, {});
|
|
case_end;
|
|
|
|
case_ast_node(ta, TypeAssertion, expr);
|
|
return escape_variant_addr(ea, ta->expr);
|
|
case_end;
|
|
|
|
case_ast_node(mie, MatrixIndexExpr, expr);
|
|
escape_visit_exits(ea, mie->row_index);
|
|
escape_visit_exits(ea, mie->column_index);
|
|
return escape_pointer_offset(escape_addr(ea, mie->expr), escape_path_of(EscapeStep_AnyElement));
|
|
case_end;
|
|
}
|
|
escape_visit_exits(ea, expr);
|
|
return escape_value();
|
|
}
|
|
|
|
|
|
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();
|
|
for (EscapeValueFact const &f : ptr) {
|
|
EscapeOrigin o = f.origin;
|
|
switch (o.kind) {
|
|
case EscapeOrigin_Local:
|
|
case EscapeOrigin_Temp:
|
|
case EscapeOrigin_Owned: {
|
|
Type *t = base_type(escape_type_at(o.obj, o.path));
|
|
|
|
bool holds_buffer = t != nullptr && (t->kind == Type_DynamicArray || t->kind == Type_Map);
|
|
|
|
if (holds_buffer && !(o.kind == EscapeOrigin_Local && escape_is_param(o.obj.entity))) {
|
|
o.kind = EscapeOrigin_Owned;
|
|
o.path = escape_path_concat(o.path, escape_path_of(EscapeStep_AnyElement));
|
|
escape_value_add(&v, {}, o);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
EscapeValue single = escape_value();
|
|
escape_value_add(&single, {}, o);
|
|
for (EscapeValueFact const &g : escape_load(ea, single, nullptr)) {
|
|
escape_value_add(&v, g.path, g.origin);
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
// what is loaded through the pointers of a value, at any depth
|
|
gb_internal EscapeValue escape_reachable(EscapeAnalysis *ea, EscapeValue const &v) {
|
|
EscapeValue reached = escape_value();
|
|
EscapeValue frontier = escape_as_pointer(v);
|
|
while (frontier.count > 0) {
|
|
EscapeValue next = escape_value();
|
|
for (EscapeValueFact const &f : escape_load_once(ea, frontier)) {
|
|
isize count = reached.count;
|
|
escape_value_add(&reached, {}, f.origin);
|
|
if (reached.count != count) {
|
|
escape_value_add(&next, {}, f.origin);
|
|
}
|
|
}
|
|
frontier = next;
|
|
}
|
|
return reached;
|
|
}
|
|
|
|
// the path of a parameter's origin is where in its value the pointer is, then a deref and the offset into what it points to
|
|
gb_internal EscapeValue escape_arg_pointers(EscapeValue const &arg, Type *type, EscapePath const &path, Type **pointee) {
|
|
isize deref = escape_path_deref_index(path);
|
|
EscapePath value_path = slice(path, 0, deref);
|
|
EscapePath offset = {};
|
|
if (deref < path.count) {
|
|
offset = slice(path, deref+1, path.count);
|
|
}
|
|
*pointee = escape_type_within(escape_pointee_type(escape_type_within(type, value_path)), offset);
|
|
if (*pointee == nullptr) {
|
|
// e.g. the offset was made through a cast in the callee
|
|
offset = {};
|
|
}
|
|
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);
|
|
|
|
Entity *e = nullptr;
|
|
Ast *proc = unparen_expr(ce->proc);
|
|
if (proc->kind == Ast_Ident || proc->kind == Ast_SelectorExpr) {
|
|
e = entity_of_node(proc);
|
|
}
|
|
|
|
Array<EscapeValue> results = {};
|
|
if (e != nullptr && e->kind == Entity_Builtin) {
|
|
results = array_make<EscapeValue>(temporary_allocator(), 0, 1);
|
|
switch (e->Builtin.id) {
|
|
case BuiltinProc_ptr_offset:
|
|
if (ce->args.count == 2) {
|
|
array_add(&results, escape_expr(ea, ce->args[0]));
|
|
escape_visit_exits(ea, ce->args[1]);
|
|
return results;
|
|
}
|
|
break;
|
|
case BuiltinProc_raw_data:
|
|
if (ce->args.count == 1) {
|
|
array_add(&results, escape_elems_of(ea, ce->args[0]));
|
|
return results;
|
|
}
|
|
break;
|
|
case BuiltinProc_mem_copy:
|
|
case BuiltinProc_mem_copy_non_overlapping:
|
|
if (ce->args.count == 3) {
|
|
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)) {
|
|
escape_store_through(ea, call, dst, escape_load(ea, src, nullptr), EscapeUpdate_Add);
|
|
}
|
|
return results;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
TypeProc *pt = nullptr;
|
|
Slice<EscapeFlow> flows = {};
|
|
if (ce->args.count > 0 && e != nullptr && e->kind == Entity_Procedure) {
|
|
// with the constant parameters of a specialization, as its arguments and summary have them
|
|
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);
|
|
}
|
|
}
|
|
if (flows.count == 0) {
|
|
for (Ast *arg : ce->args) {
|
|
if (arg->kind == Ast_FieldValue) {
|
|
arg = arg->FieldValue.value;
|
|
}
|
|
escape_visit_exits(ea, arg);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
results = array_make<EscapeValue>(temporary_allocator(), pt->result_count);
|
|
for (EscapeValue &r : results) {
|
|
r = escape_value();
|
|
}
|
|
|
|
// the value of each argument which a flow involves
|
|
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;
|
|
}
|
|
|
|
bool packs_variadic = pt->variadic && !pt->c_vararg && ce->ellipsis.pos.line == 0;
|
|
for_array(i, ce->args) {
|
|
Ast *arg = ce->args[i];
|
|
Ast *value = arg;
|
|
if (arg->kind == Ast_FieldValue) {
|
|
value = arg->FieldValue.value;
|
|
}
|
|
|
|
isize index = 0;
|
|
bool involved = false;
|
|
if (escape_param_index_of_arg(pt, arg, i, &index)) {
|
|
for (EscapeFlow const &flow : flows) {
|
|
involved |= flow.param == index;
|
|
switch (flow.target) {
|
|
case EscapeFlowTarget_Pointee:
|
|
case EscapeFlowTarget_Loaded:
|
|
case EscapeFlowTarget_Deep:
|
|
involved |= flow.target_index == index;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!involved) {
|
|
escape_visit_exits(ea, value);
|
|
continue;
|
|
}
|
|
|
|
Type *type = pt->params->Tuple.variables[index]->type;
|
|
given[index] = true;
|
|
types[index] = value->tav.type;
|
|
if (packs_variadic && index == pt->variadic_index && arg->kind != Ast_FieldValue) {
|
|
types[index] = type;
|
|
// these are passed in a slice in the stack frame of this procedure
|
|
Type *slice = base_type(type);
|
|
Type *elem = nullptr;
|
|
if (slice->kind == Type_Slice) {
|
|
elem = slice->Slice.elem;
|
|
}
|
|
EscapeValue v = escape_convert(ea, escape_expr(ea, value), value, elem);
|
|
escape_store_obj(ea, {nullptr, call}, escape_path_of(EscapeStep_AnyElement), v, EscapeUpdate_Add);
|
|
args[index] = escape_value_of(EscapeOrigin_Temp, {nullptr, call}, escape_path_of(EscapeStep_AnyElement));
|
|
continue;
|
|
}
|
|
args[index] = escape_convert(ea, escape_expr(ea, value), value, type);
|
|
}
|
|
|
|
for (EscapeFlow const &flow : flows) {
|
|
if (!given[flow.param]) {
|
|
continue;
|
|
}
|
|
EscapeValue v = {};
|
|
if (flow.kind == EscapeFlow_Value && escape_path_deref_index(flow.param_path) == flow.param_path.count) {
|
|
v = escape_value_project(args[flow.param], flow.param_path);
|
|
} else {
|
|
Type *pointee = nullptr;
|
|
v = escape_arg_pointers(args[flow.param], types[flow.param], flow.param_path, &pointee);
|
|
if (flow.kind != EscapeFlow_Value) {
|
|
if (pointee != nullptr && !escape_type_has_pointers(pointee)) {
|
|
continue;
|
|
}
|
|
v = escape_load_once(ea, v);
|
|
}
|
|
if (flow.kind == EscapeFlow_Deep) {
|
|
v = escape_reachable(ea, v);
|
|
}
|
|
}
|
|
|
|
switch (flow.target) {
|
|
case EscapeFlowTarget_Result:
|
|
for (EscapeValueFact const &f : v) {
|
|
escape_value_add(&results[flow.target_index], escape_path_concat(flow.target_path, f.path), f.origin);
|
|
}
|
|
break;
|
|
|
|
case EscapeFlowTarget_Pointee:
|
|
case EscapeFlowTarget_Loaded:
|
|
case EscapeFlowTarget_Deep:
|
|
if (given[flow.target_index]) {
|
|
Type *pointee = nullptr;
|
|
EscapeValue ptr = escape_arg_pointers(args[flow.target_index], types[flow.target_index], flow.target_path, &pointee);
|
|
if (flow.target != EscapeFlowTarget_Pointee) {
|
|
ptr = escape_as_pointer(escape_load_once(ea, ptr));
|
|
}
|
|
if (flow.target == EscapeFlowTarget_Deep) {
|
|
ptr = escape_reachable(ea, ptr);
|
|
}
|
|
escape_store_through(ea, call, ptr, v, EscapeUpdate_Add);
|
|
}
|
|
break;
|
|
|
|
case EscapeFlowTarget_Outer:
|
|
escape_store_through(ea, call, escape_value_of(EscapeOrigin_Outer, {}, {}), v, EscapeUpdate_Add);
|
|
break;
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
// the call of an expression of several values, e.g. `x, ok := f()`
|
|
gb_internal Ast *escape_call_of(Ast *expr) {
|
|
expr = unparen_expr(expr);
|
|
if (expr->kind == Ast_SelectorCallExpr) {
|
|
expr = expr->SelectorCallExpr.call;
|
|
}
|
|
if (expr->kind == Ast_CallExpr) {
|
|
return expr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
gb_internal EscapeValue escape_expr(EscapeAnalysis *ea, Ast *expr) {
|
|
if (expr == nullptr) {
|
|
return escape_value();
|
|
}
|
|
|
|
TypeAndValue const &tav = expr->tav;
|
|
if (tav.mode == Addressing_Constant) {
|
|
if (tav.type == nullptr || !is_type_slice(tav.type) || is_load_directive_call(expr)) {
|
|
return escape_value();
|
|
}
|
|
if (expr->kind == Ast_CompoundLit && expr->CompoundLit.elems.count == 0) {
|
|
return escape_value();
|
|
}
|
|
// a constant slice uses the memory of the stack frame
|
|
return escape_value_of(EscapeOrigin_Temp, {nullptr, expr}, escape_path_of(EscapeStep_AnyElement));
|
|
}
|
|
|
|
if (tav.type != nullptr && escape_type_is_plain(tav.type) &&
|
|
(expr->viral_state_flags & (ViralStateFlag_ContainsOrReturn|ViralStateFlag_ContainsOrBreak)) == 0) {
|
|
escape_visit_exits(ea, expr);
|
|
return escape_value();
|
|
}
|
|
|
|
switch (expr->kind) {
|
|
case_ast_node(pe, ParenExpr, expr);
|
|
return escape_expr(ea, pe->expr);
|
|
case_end;
|
|
|
|
case_ast_node(i, Ident, expr);
|
|
return escape_ident_value(ea, entity_of_node(expr));
|
|
case_end;
|
|
|
|
case_ast_node(cl, CompoundLit, expr);
|
|
EscapeValue v = escape_value();
|
|
Type *t = base_type(expr->tav.type);
|
|
if (t == nullptr) {
|
|
return v;
|
|
}
|
|
for_array(i, cl->elems) {
|
|
Ast *elem = cl->elems[i];
|
|
Ast *value = elem;
|
|
EscapePath path = escape_path_of(EscapeStep_AnyElement);
|
|
Type *elem_type = nullptr;
|
|
if (elem->kind == Ast_FieldValue) {
|
|
value = elem->FieldValue.value;
|
|
}
|
|
|
|
switch (t->kind) {
|
|
case Type_Struct: {
|
|
Entity *f = nullptr;
|
|
if (elem->kind == Ast_FieldValue) {
|
|
Ast *field = elem->FieldValue.field;
|
|
if (field->kind == Ast_Ident) {
|
|
for (Entity *g : t->Struct.fields) {
|
|
if (g->token.string == field->Ident.token.string) {
|
|
f = g;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (i < t->Struct.fields.count) {
|
|
f = t->Struct.fields[i];
|
|
}
|
|
if (f != nullptr && !t->Struct.is_raw_union) {
|
|
path = escape_path_of(EscapeStep_Field, f);
|
|
} else {
|
|
path = EscapePath{};
|
|
}
|
|
|
|
if (f != nullptr) {
|
|
elem_type = f->type;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Type_Array: elem_type = t->Array.elem; break;
|
|
case Type_EnumeratedArray: elem_type = t->EnumeratedArray.elem; break;
|
|
case Type_FixedCapacityDynamicArray: elem_type = t->FixedCapacityDynamicArray.elem; break;
|
|
case Type_Slice: elem_type = t->Slice.elem; break;
|
|
case Type_DynamicArray: elem_type = t->DynamicArray.elem; break;
|
|
case Type_Map: elem_type = t->Map.value; break;
|
|
|
|
default:
|
|
path = {};
|
|
break;
|
|
}
|
|
if (elem_type != nullptr && escape_type_is_plain(elem_type)) {
|
|
escape_visit_exits(ea, value);
|
|
continue;
|
|
}
|
|
EscapeValue ev = escape_convert(ea, escape_expr(ea, value), value, elem_type);
|
|
for (EscapeValueFact const &f : ev) {
|
|
escape_value_add(&v, escape_path_concat(path, f.path), f.origin, f.definite);
|
|
}
|
|
}
|
|
|
|
if (t->kind == Type_Slice) {
|
|
if (cl->elems.count == 0) {
|
|
return escape_value();
|
|
}
|
|
escape_store_obj(ea, {nullptr, expr}, {}, v, EscapeUpdate_Add);
|
|
return escape_value_of(EscapeOrigin_Temp, {nullptr, expr}, escape_path_of(EscapeStep_AnyElement));
|
|
}
|
|
return v;
|
|
case_end;
|
|
|
|
case_ast_node(ue, UnaryExpr, expr);
|
|
if (ue->op.kind == Token_And) {
|
|
return escape_addr(ea, unparen_expr(ue->expr));
|
|
}
|
|
escape_visit_exits(ea, ue->expr);
|
|
return escape_value();
|
|
case_end;
|
|
|
|
case_ast_node(be, BinaryExpr, expr);
|
|
escape_visit_exits(ea, be->left);
|
|
escape_visit_exits(ea, be->right);
|
|
return escape_value();
|
|
case_end;
|
|
|
|
case_ast_node(se, SelectorExpr, expr);
|
|
if (Entity *g = escape_package_selector(expr)) {
|
|
return escape_ident_value(ea, g);
|
|
}
|
|
EscapePath path = {};
|
|
if (!escape_selector_path(expr, &path)) {
|
|
escape_visit_exits(ea, se->expr);
|
|
return escape_value();
|
|
}
|
|
if (is_type_pointer(se->expr->tav.type)) {
|
|
return escape_load(ea, escape_pointer_offset(escape_expr(ea, se->expr), path), tav.type);
|
|
}
|
|
return escape_value_project(escape_expr(ea, se->expr), path);
|
|
case_end;
|
|
|
|
case_ast_node(ie, IndexExpr, expr);
|
|
Type *t = base_type(ie->expr->tav.type);
|
|
if (t == nullptr) {
|
|
escape_visit_exits(ea, ie->index);
|
|
return escape_value();
|
|
}
|
|
switch (t->kind) {
|
|
case Type_Array:
|
|
case Type_EnumeratedArray:
|
|
case Type_FixedCapacityDynamicArray:
|
|
case Type_DynamicArray:
|
|
case Type_Map: {
|
|
EscapeValue v = escape_value_project(escape_expr(ea, ie->expr), escape_path_of(EscapeStep_AnyElement));
|
|
escape_visit_exits(ea, ie->index);
|
|
return v;
|
|
}
|
|
case Type_Pointer:
|
|
case Type_Slice:
|
|
case Type_MultiPointer: {
|
|
EscapeValue ptr = escape_elems_of(ea, ie->expr);
|
|
escape_visit_exits(ea, ie->index);
|
|
return escape_load(ea, ptr, tav.type);
|
|
}
|
|
}
|
|
escape_visit_exits(ea, ie->expr);
|
|
escape_visit_exits(ea, ie->index);
|
|
return escape_value();
|
|
case_end;
|
|
|
|
case_ast_node(de, DerefExpr, expr);
|
|
return escape_load(ea, escape_expr(ea, de->expr), tav.type);
|
|
case_end;
|
|
|
|
case_ast_node(se, SliceExpr, expr);
|
|
escape_visit_exits(ea, se->low);
|
|
escape_visit_exits(ea, se->high);
|
|
return escape_elems_of(ea, se->expr);
|
|
case_end;
|
|
|
|
case_ast_node(ce, CallExpr, expr);
|
|
if (ce->proc->tav.mode == Addressing_Type) {
|
|
if (ce->args.count != 1) {
|
|
return escape_value();
|
|
}
|
|
Ast *arg = ce->args[0];
|
|
EscapeValue v = escape_expr(ea, arg);
|
|
if (escape_is_explicit_unsafe_conversion(tav.type)) {
|
|
return escape_value();
|
|
}
|
|
return escape_convert(ea, v, arg, tav.type);
|
|
}
|
|
Array<EscapeValue> results = escape_call(ea, expr);
|
|
if (results.count > 0) {
|
|
return results[0];
|
|
}
|
|
return escape_value();
|
|
case_end;
|
|
|
|
case_ast_node(sce, SelectorCallExpr, expr);
|
|
return escape_expr(ea, sce->call);
|
|
case_end;
|
|
|
|
case_ast_node(tc, TypeCast, expr);
|
|
EscapeValue v = escape_expr(ea, tc->expr);
|
|
if (tc->token.kind == Token_transmute ||
|
|
escape_is_explicit_unsafe_conversion(tav.type)) {
|
|
return escape_value();
|
|
}
|
|
return escape_convert(ea, v, tc->expr, tav.type);
|
|
case_end;
|
|
|
|
case_ast_node(ac, AutoCast, expr);
|
|
return escape_convert(ea, escape_expr(ea, ac->expr), ac->expr, tav.type);
|
|
case_end;
|
|
|
|
case_ast_node(te, TernaryIfExpr, expr);
|
|
escape_visit_exits(ea, te->cond);
|
|
EscapeValue v = escape_expr(ea, te->x);
|
|
escape_value_merge(&v, escape_expr(ea, te->y));
|
|
return v;
|
|
case_end;
|
|
|
|
case_ast_node(te, TernaryWhenExpr, expr);
|
|
if (te->cond == nullptr || te->cond->tav.value.kind != ExactValue_Bool) {
|
|
return escape_value();
|
|
}
|
|
if (te->cond->tav.value.value_bool) {
|
|
return escape_expr(ea, te->x);
|
|
}
|
|
return escape_expr(ea, te->y);
|
|
case_end;
|
|
|
|
case_ast_node(oe, OrElseExpr, expr);
|
|
EscapeValue v = escape_expr(ea, oe->x);
|
|
escape_value_merge(&v, escape_expr(ea, oe->y));
|
|
return v;
|
|
case_end;
|
|
|
|
case_ast_node(re, OrReturnExpr, expr);
|
|
EscapeValue v = escape_expr(ea, re->expr);
|
|
EscapeState state = escape_state_clone(ea->state);
|
|
escape_exit(ea, expr, {}, {});
|
|
ea->state = state;
|
|
return v;
|
|
case_end;
|
|
|
|
case_ast_node(be, OrBranchExpr, expr);
|
|
EscapeValue v = escape_expr(ea, be->expr);
|
|
EscapeState state = escape_state_clone(ea->state);
|
|
switch (be->token.kind) {
|
|
case Token_or_break: escape_branch(ea, be->label, Token_break); break;
|
|
case Token_or_continue: escape_branch(ea, be->label, Token_continue); break;
|
|
default: GB_PANIC("Unhandled OrBranchExpr"); break;
|
|
}
|
|
ea->state = state;
|
|
return v;
|
|
case_end;
|
|
|
|
case_ast_node(ta, TypeAssertion, expr);
|
|
Type *t = ta->expr->tav.type;
|
|
if (is_type_any(t) || is_type_pointer(t)) {
|
|
return escape_load(ea, escape_variant_addr(ea, ta->expr), tav.type);
|
|
}
|
|
|
|
EscapeValue w = escape_value();
|
|
for (EscapeValueFact const &f : escape_expr(ea, ta->expr)) {
|
|
escape_value_add(&w, {}, f.origin, f.definite);
|
|
}
|
|
return w;
|
|
case_end;
|
|
|
|
case_ast_node(te, TagExpr, expr);
|
|
return escape_expr(ea, te->expr);
|
|
case_end;
|
|
|
|
case_ast_node(fv, FieldValue, expr);
|
|
return escape_expr(ea, fv->value);
|
|
case_end;
|
|
}
|
|
return escape_value();
|
|
}
|
|
|
|
|
|
|
|
gb_internal bool escape_is_stack(EscapeOrigin const &o) {
|
|
switch (o.kind) {
|
|
case EscapeOrigin_Local:
|
|
case EscapeOrigin_Temp:
|
|
case EscapeOrigin_Variadic:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
gb_internal bool escape_find_stack_origin(EscapeAnalysis *ea, EscapeValue const &v, EscapeOrigin *found, bool *definite=nullptr) {
|
|
for (EscapeValueFact const &f : v) {
|
|
if (f.definite && escape_is_stack(f.origin)) {
|
|
*found = f.origin;
|
|
if (definite) {
|
|
*definite = true;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
auto work = array_make<EscapeOrigin>(temporary_allocator(), 0, 0);
|
|
for (EscapeValueFact const &f : v) {
|
|
array_add(&work, f.origin);
|
|
}
|
|
for (isize i = 0; i < work.count; i++) {
|
|
EscapeOrigin o = work[i];
|
|
if (escape_is_stack(o)) {
|
|
*found = o;
|
|
return true;
|
|
}
|
|
if (o.kind != EscapeOrigin_Owned) {
|
|
continue;
|
|
}
|
|
|
|
for (EscapeFact const &f : ea->state.facts) {
|
|
if (!escape_object_eq(f.obj, o.obj)) {
|
|
continue;
|
|
}
|
|
if (!escape_path_has_prefix(f.path, o.path) && !escape_path_has_prefix(o.path, f.path)) {
|
|
continue;
|
|
}
|
|
|
|
bool seen = false;
|
|
for (EscapeOrigin const &w : work) {
|
|
if (escape_origin_eq(w, f.origin)) {
|
|
seen = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!seen) {
|
|
array_add(&work, f.origin);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// a long expression, e.g. a large compound literal, is abbreviated
|
|
gb_internal gbString escape_expr_to_string(Ast *expr) {
|
|
gbString s = expr_to_string(expr);
|
|
if (gb_string_length(s) > 80) {
|
|
gb_string_free(s);
|
|
s = expr_to_string_shorthand(expr);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
gb_internal void escape_report_emit(EscapeReport const &r) {
|
|
Ast *node = r.node;
|
|
String expr_str = r.expr_str;
|
|
EscapeOrigin const &o = r.origin;
|
|
EscapeReportKind kind = r.kind;
|
|
|
|
gbString origin = gb_string_make(heap_allocator(), "");
|
|
defer (gb_string_free(origin));
|
|
switch (o.kind) {
|
|
case EscapeOrigin_Local: {
|
|
char const *description = "local variable";
|
|
if (o.obj.entity->flags & EntityFlag_Param) {
|
|
description = "parameter";
|
|
}
|
|
origin = gb_string_append_fmt(origin, "the stack memory of the %s '%.*s'", description, LIT(o.obj.entity->token.string));
|
|
break;
|
|
}
|
|
case EscapeOrigin_Temp: {
|
|
char const *description = "a temporary value";
|
|
if (o.obj.temp->kind == Ast_CompoundLit) {
|
|
description = "a compound literal";
|
|
}
|
|
origin = gb_string_append_fmt(origin, "the stack memory of %s", description);
|
|
break;
|
|
}
|
|
case EscapeOrigin_Variadic:
|
|
origin = gb_string_append_fmt(origin, "the variadic parameter '%.*s', which only lives for the duration of the call", LIT(o.obj.entity->token.string));
|
|
break;
|
|
}
|
|
|
|
ERROR_BLOCK();
|
|
switch (kind) {
|
|
case EscapeReport_Return:
|
|
error(ast_token(node), "It is unsafe to return '%.*s' from a procedure, as it refers to %s", LIT(expr_str), origin);
|
|
break;
|
|
case EscapeReport_MayReturn:
|
|
warning(ast_token(node), "It is unsafe to return '%.*s' from a procedure, as it may refer to %s", LIT(expr_str), origin);
|
|
break;
|
|
case EscapeReport_Store:
|
|
warning(ast_token(node), "'%.*s' may still refer to %s after the procedure returns", LIT(expr_str), origin);
|
|
break;
|
|
case EscapeReport_CallStore:
|
|
warning(ast_token(node), "'%.*s' may keep a reference to %s after the procedure returns", LIT(expr_str), origin);
|
|
break;
|
|
default:
|
|
GB_PANIC("Unhandled EscapeReportKind");
|
|
break;
|
|
}
|
|
|
|
if (kind == EscapeReport_Store) {
|
|
error_line("\tSuggestion: Clear it before the procedure returns, e.g. 'defer %.*s = %s'\n", LIT(expr_str), type_zero_value_string(node->tav.type));
|
|
} else {
|
|
error_line("\tSuggestion: If this is intended, make it explicit with a conversion to 'rawptr' or 'uintptr', or a 'transmute'\n");
|
|
}
|
|
}
|
|
|
|
gb_internal void escape_report(EscapeAnalysis *ea, Ast *node, String expr_str, EscapeOrigin const &o, EscapeReportKind kind) {
|
|
if (ea->muted || (kind != EscapeReport_Return && global_ignore_warnings())) {
|
|
return;
|
|
}
|
|
for (Ast *r : ea->reported) {
|
|
if (r == node) {
|
|
return;
|
|
}
|
|
}
|
|
array_add(&ea->reported, node);
|
|
|
|
EscapeReport r = {kind, node, expr_str, o};
|
|
if (ea->reports == nullptr) {
|
|
escape_report_emit(r);
|
|
return;
|
|
}
|
|
// kept beyond this analysis and its temporary memory
|
|
r.expr_str = copy_string(permanent_allocator(), expr_str);
|
|
r.origin.path = {};
|
|
array_add(ea->reports, r);
|
|
}
|
|
|
|
gb_internal void escape_report_value(EscapeAnalysis *ea, Ast *node, Ast *expr, String name, EscapeValue const &v) {
|
|
EscapeOrigin o = {};
|
|
bool definite = false;
|
|
if (!escape_find_stack_origin(ea, v, &o, &definite)) {
|
|
return;
|
|
}
|
|
|
|
EscapeReportKind kind = EscapeReport_MayReturn;
|
|
if (definite) {
|
|
kind = EscapeReport_Return;
|
|
}
|
|
|
|
if (expr != nullptr) {
|
|
gbString s = escape_expr_to_string(expr);
|
|
escape_report(ea, node, make_string_c(s), o, kind);
|
|
gb_string_free(s);
|
|
} else {
|
|
escape_report(ea, node, name, o, kind);
|
|
}
|
|
}
|
|
|
|
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]);
|
|
for (isize j = defers.count-1; j >= 0; j--) {
|
|
escape_stmt(ea, defers[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
gb_internal bool escape_param_index(TypeProc *pt, Entity *e, isize *index) {
|
|
if (pt->params == nullptr) {
|
|
return false;
|
|
}
|
|
for_array(i, pt->params->Tuple.variables) {
|
|
if (pt->params->Tuple.variables[i] == e) {
|
|
*index = i;
|
|
return true;
|
|
}
|
|
}
|
|
// e.g. 'context'
|
|
return false;
|
|
}
|
|
|
|
// a flow from the parameter whose memory the origin is
|
|
gb_internal bool escape_flow_from(TypeProc *pt, EscapeOrigin const &o, EscapeFlow *flow) {
|
|
switch (o.kind) {
|
|
case EscapeOrigin_Param: flow->kind = EscapeFlow_Value; break;
|
|
case EscapeOrigin_ParamLoad: flow->kind = EscapeFlow_Load; break;
|
|
case EscapeOrigin_ParamDeep: flow->kind = EscapeFlow_Deep; break;
|
|
default:
|
|
return false;
|
|
}
|
|
flow->param_path = o.path;
|
|
return escape_param_index(pt, o.obj.entity, &flow->param);
|
|
}
|
|
|
|
gb_internal bool escape_flow_eq(EscapeFlow const &a, EscapeFlow const &b) {
|
|
return a.kind == b.kind &&
|
|
a.param == b.param &&
|
|
a.target == b.target &&
|
|
a.target_index == b.target_index &&
|
|
escape_path_eq(a.param_path, b.param_path) &&
|
|
escape_path_eq(a.target_path, b.target_path);
|
|
}
|
|
|
|
// a path is cut before a step repeating an earlier one, as otherwise a recursive procedure could make its flows
|
|
// grow without end, e.g. when the elements of the dynamic array it returns hold what its calls return
|
|
gb_internal EscapePath escape_path_widen(EscapePath const &p) {
|
|
isize segment = 0;
|
|
for_array(i, p) {
|
|
if (p[i].kind == EscapeStep_Deref) {
|
|
segment = i+1;
|
|
continue;
|
|
}
|
|
for (isize j = segment; j < i; j++) {
|
|
if (p[j].kind == p[i].kind && p[j].field == p[i].field) {
|
|
return slice(p, 0, i);
|
|
}
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
gb_internal void escape_add_flow(EscapeAnalysis *ea, EscapeFlow flow) {
|
|
flow.param_path = escape_path_widen(flow.param_path);
|
|
flow.target_path = escape_path_widen(flow.target_path);
|
|
for (EscapeFlow const &g : ea->flows) {
|
|
if (escape_flow_eq(g, flow)) {
|
|
return;
|
|
}
|
|
}
|
|
array_add(&ea->flows, flow);
|
|
}
|
|
|
|
gb_internal void escape_add_result_flows(EscapeAnalysis *ea, isize result_index, EscapeValue const &v) {
|
|
for (EscapeValueFact const &f : v) {
|
|
EscapeFlow flow = {};
|
|
if (escape_flow_from(ea->pt, f.origin, &flow)) {
|
|
flow.target = EscapeFlowTarget_Result;
|
|
flow.target_index = result_index;
|
|
flow.target_path = f.path;
|
|
escape_add_flow(ea, flow);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (pt->results != nullptr) {
|
|
result_count = pt->results->Tuple.variables.count;
|
|
}
|
|
|
|
// 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) {
|
|
result = results[i];
|
|
}
|
|
escape_report_value(ea, result, result, {}, values[i]);
|
|
escape_add_result_flows(ea, i, values[i]);
|
|
}
|
|
} else if (pt->has_named_results) {
|
|
for_array(i, pt->results->Tuple.variables) {
|
|
Entity *e = pt->results->Tuple.variables[i];
|
|
EscapeValue v = escape_obj_value(ea, {e}, {});
|
|
escape_report_value(ea, node, nullptr, e->token.string, v);
|
|
escape_add_result_flows(ea, i, v);
|
|
}
|
|
}
|
|
|
|
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 = {};
|
|
if (escape_find_stack_origin(ea, v, &o) && o.kind != EscapeOrigin_Variadic) {
|
|
EscapeReportKind kind = EscapeReport_Store;
|
|
if (s.node->kind == Ast_CallExpr) {
|
|
kind = EscapeReport_CallStore;
|
|
}
|
|
gbString str = escape_expr_to_string(s.node);
|
|
escape_report(ea, s.node, make_string_c(str), o, kind);
|
|
gb_string_free(str);
|
|
}
|
|
}
|
|
ea->state.reachable = false;
|
|
}
|
|
|
|
// returns whether the location is exact, so that a later store to it replaces this one
|
|
gb_internal bool escape_outer_location(Ast *lhs, Entity **root, EscapePath *path) {
|
|
lhs = unparen_expr(lhs);
|
|
*root = nullptr;
|
|
*path = {};
|
|
switch (lhs->kind) {
|
|
case_ast_node(i, Ident, lhs);
|
|
*root = entity_of_node(lhs);
|
|
return *root != nullptr;
|
|
case_end;
|
|
case_ast_node(se, SelectorExpr, lhs);
|
|
if (Entity *g = escape_package_selector(lhs)) {
|
|
*root = g;
|
|
return true;
|
|
}
|
|
|
|
EscapePath field = {};
|
|
if (!escape_selector_path(lhs, &field)) {
|
|
return false;
|
|
}
|
|
|
|
bool exact = escape_outer_location(se->expr, root, path);
|
|
if (is_type_pointer(se->expr->tav.type)) {
|
|
*path = escape_path_concat(*path, escape_path_of(EscapeStep_Deref));
|
|
}
|
|
*path = escape_path_concat(*path, field);
|
|
return exact;
|
|
case_end;
|
|
case_ast_node(ie, IndexExpr, lhs);
|
|
escape_outer_location(ie->expr, root, path);
|
|
*path = escape_path_concat(*path, escape_path_of(EscapeStep_AnyElement));
|
|
return false;
|
|
case_end;
|
|
case_ast_node(de, DerefExpr, lhs);
|
|
bool exact = escape_outer_location(de->expr, root, path);
|
|
*path = escape_path_concat(*path, escape_path_of(EscapeStep_Deref));
|
|
return exact;
|
|
case_end;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
gb_internal void escape_store(EscapeAnalysis *ea, Ast *lhs, EscapeValue const &v) {
|
|
lhs = unparen_expr(lhs);
|
|
if (lhs == nullptr || is_blank_ident(lhs)) {
|
|
return;
|
|
}
|
|
if (lhs->kind == Ast_Ident) {
|
|
Entity *e = entity_of_node(lhs);
|
|
if (escape_is_local(e) && e->using_parent == nullptr && !escape_is_reference(e)) {
|
|
escape_store_obj(ea, {e}, {}, v, EscapeUpdate_Replace);
|
|
return;
|
|
}
|
|
}
|
|
|
|
EscapeValue ptr = escape_addr(ea, lhs);
|
|
// with only one place it may store to, the store replaces what that held
|
|
EscapeUpdateKind update = EscapeUpdate_Add;
|
|
if (ptr.count == 1) {
|
|
update = EscapeUpdate_Replace;
|
|
}
|
|
escape_store_through(ea, lhs, ptr, v, update);
|
|
}
|
|
|
|
// `node` is the left hand side, or a call storing into memory it was given
|
|
gb_internal void escape_store_through(EscapeAnalysis *ea, Ast *node, EscapeValue const &ptr, EscapeValue const &v, EscapeUpdateKind update) {
|
|
for (EscapeValueFact const &f : ptr) {
|
|
EscapeOrigin const &o = f.origin;
|
|
switch (o.kind) {
|
|
case EscapeOrigin_Local:
|
|
case EscapeOrigin_Temp:
|
|
case EscapeOrigin_Owned:
|
|
escape_store_obj(ea, o.obj, o.path, v, update);
|
|
break;
|
|
|
|
case EscapeOrigin_Outer:
|
|
case EscapeOrigin_Param:
|
|
case EscapeOrigin_ParamLoad:
|
|
case EscapeOrigin_ParamDeep: {
|
|
EscapeOuterStore s = {};
|
|
s.node = node;
|
|
s.dest = o;
|
|
s.exact = escape_outer_location(node, &s.root, &s.path);
|
|
if (s.exact) {
|
|
for (isize i = ea->state.outers.count-1; i >= 0; i--) {
|
|
EscapeOuterStore const &p = ea->state.outers[i];
|
|
if (p.root == s.root && escape_path_eq(p.path, s.path)) {
|
|
array_unordered_remove(&ea->state.outers, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (EscapeValueFact const &vf : v) {
|
|
s.node = node;
|
|
s.origin = vf.origin;
|
|
switch (vf.origin.kind) {
|
|
case EscapeOrigin_Outer:
|
|
continue;
|
|
case EscapeOrigin_Param:
|
|
case EscapeOrigin_ParamLoad:
|
|
case EscapeOrigin_ParamDeep:
|
|
// never reported, only made into a flow, so where it is stored does not need telling apart
|
|
s.node = nullptr;
|
|
break;
|
|
}
|
|
bool found = false;
|
|
for (EscapeOuterStore const &p : ea->state.outers) {
|
|
if (escape_outer_eq(p, s)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
array_add(&ea->state.outers, s);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
gb_internal isize escape_push_target(EscapeAnalysis *ea, Ast *stmt, EscapeTargetKind kind) {
|
|
EscapeTarget t = {};
|
|
t.kind = kind;
|
|
t.stmt = stmt;
|
|
t.defer_depth = ea->defers.count;
|
|
t.breaks = escape_state_unreachable();
|
|
t.continues = escape_state_unreachable();
|
|
array_add(&ea->targets, t);
|
|
return ea->targets.count-1;
|
|
}
|
|
|
|
gb_internal void escape_branch(EscapeAnalysis *ea, Ast *label, TokenKind kind) {
|
|
Ast *labelled = nullptr;
|
|
if (label != nullptr) {
|
|
Entity *e = entity_of_node(label);
|
|
if (e == nullptr || e->kind != Entity_Label) {
|
|
ea->state.reachable = false;
|
|
return;
|
|
}
|
|
labelled = e->Label.parent;
|
|
}
|
|
|
|
for (isize i = ea->targets.count-1; i >= 0; i--) {
|
|
EscapeTarget const &target = ea->targets[i];
|
|
if (labelled != nullptr) {
|
|
if (target.stmt != labelled) {
|
|
continue;
|
|
}
|
|
} else if (kind == Token_continue) {
|
|
if (target.kind != EscapeTarget_Loop) {
|
|
continue;
|
|
}
|
|
} else if (target.kind != EscapeTarget_Loop && target.kind != EscapeTarget_Switch) {
|
|
continue;
|
|
}
|
|
|
|
escape_run_defers(ea, target.defer_depth);
|
|
// the defers may have added targets, which can move them
|
|
if (kind == Token_continue) {
|
|
escape_state_join(&ea->targets[i].continues, ea->state);
|
|
} else {
|
|
escape_state_join(&ea->targets[i].breaks, ea->state);
|
|
}
|
|
break;
|
|
}
|
|
|
|
ea->state.reachable = false;
|
|
}
|
|
|
|
gb_internal void escape_block(EscapeAnalysis *ea, Slice<Ast *> const &stmts) {
|
|
array_add(&ea->defers, array_make<Ast *>(temporary_allocator(), 0, 0));
|
|
for (Ast *stmt : stmts) {
|
|
if (!ea->state.reachable) {
|
|
break;
|
|
}
|
|
escape_stmt(ea, stmt);
|
|
}
|
|
|
|
if (ea->state.reachable) {
|
|
escape_run_defers(ea, ea->defers.count-1);
|
|
}
|
|
array_pop(&ea->defers);
|
|
|
|
for (Ast *stmt : stmts) {
|
|
escape_forget(ea, stmt);
|
|
}
|
|
}
|
|
|
|
|
|
gb_internal void escape_loop(EscapeAnalysis *ea, Ast *stmt, Ast *cond, Ast *post, Ast *body, Ast *val0, Ast *val1, Ast *range) {
|
|
isize target_index = escape_push_target(ea, stmt, EscapeTarget_Loop);
|
|
isize alias_count = ea->aliases.count;
|
|
|
|
EscapeState head = escape_state_clone(ea->state);
|
|
EscapeState exit = escape_state_unreachable();
|
|
|
|
enum : isize { MAX_ITERATION_COUNT = 16 };
|
|
|
|
for (isize iteration = 0; iteration < MAX_ITERATION_COUNT; iteration++) {
|
|
ea->state = escape_state_clone(head);
|
|
escape_visit_exits(ea, cond);
|
|
|
|
bool may_exit = range != nullptr;
|
|
if (cond != nullptr) {
|
|
may_exit = true;
|
|
if (cond->tav.mode == Addressing_Constant &&
|
|
cond->tav.value.kind == ExactValue_Bool &&
|
|
cond->tav.value.value_bool) {
|
|
may_exit = false;
|
|
}
|
|
}
|
|
if (may_exit) {
|
|
escape_state_join(&exit, ea->state);
|
|
}
|
|
|
|
if (range != nullptr) {
|
|
Ast *x = unparen_expr(range);
|
|
Type *t = base_type(x->tav.type);
|
|
Type *container = t;
|
|
if (t != nullptr && t->kind == Type_Pointer) {
|
|
container = base_type(t->Pointer.elem);
|
|
}
|
|
TypeKind container_kind = Type_Invalid;
|
|
if (container != nullptr) {
|
|
container_kind = container->kind;
|
|
}
|
|
|
|
switch (container_kind) {
|
|
case Type_Array:
|
|
case Type_EnumeratedArray:
|
|
case Type_FixedCapacityDynamicArray:
|
|
case Type_Slice:
|
|
case Type_DynamicArray:
|
|
case Type_Map: {
|
|
EscapeValue ptr = escape_elems_of(ea, x);
|
|
EscapeValue v = escape_load(ea, ptr, nullptr);
|
|
if (t->kind != Type_Pointer && t->kind != Type_Slice) {
|
|
// the container may not be addressable, e.g. the result of a call
|
|
escape_value_merge(&v, escape_value_project(escape_expr(ea, x), escape_path_of(EscapeStep_AnyElement)));
|
|
}
|
|
|
|
Ast *name = val0;
|
|
if (container_kind == Type_Map) {
|
|
name = val1;
|
|
}
|
|
bool by_ref = false;
|
|
if (name != nullptr && name->kind == Ast_UnaryExpr && name->UnaryExpr.op.kind == Token_And) {
|
|
name = name->UnaryExpr.expr;
|
|
by_ref = true;
|
|
}
|
|
|
|
Entity *e = nullptr;
|
|
if (name != nullptr) {
|
|
e = entity_of_node(name);
|
|
}
|
|
if (e == nullptr) {
|
|
break;
|
|
}
|
|
|
|
if (by_ref) {
|
|
for (EscapeValueFact const &f : ptr) {
|
|
array_add(&ea->aliases, EscapeAlias{e, f.origin});
|
|
}
|
|
} else {
|
|
escape_store_obj(ea, {e}, {}, v, EscapeUpdate_Replace);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
escape_visit_exits(ea, x);
|
|
break;
|
|
}
|
|
}
|
|
|
|
escape_stmt(ea, body);
|
|
escape_state_join(&ea->state, ea->targets[target_index].continues);
|
|
if (ea->state.reachable && post != nullptr) {
|
|
escape_stmt(ea, post);
|
|
}
|
|
|
|
ea->aliases.count = alias_count;
|
|
|
|
Ast *vals[2] = {val0, val1};
|
|
for (Ast *val : vals) {
|
|
if (val != nullptr && val->kind == Ast_UnaryExpr) {
|
|
val = val->UnaryExpr.expr;
|
|
}
|
|
if (val != nullptr && val->kind == Ast_Ident) {
|
|
escape_forget_entity(ea, entity_of_node(val));
|
|
}
|
|
}
|
|
if (!escape_state_join(&head, ea->state)) {
|
|
break;
|
|
}
|
|
}
|
|
escape_state_join(&exit, ea->targets[target_index].breaks);
|
|
array_pop(&ea->targets);
|
|
ea->state = exit;
|
|
}
|
|
|
|
gb_internal void escape_switch(EscapeAnalysis *ea, Ast *stmt, Ast *body, Ast *type_switch_rhs, bool exhaustive) {
|
|
if (body == nullptr || body->kind != Ast_BlockStmt) {
|
|
return;
|
|
}
|
|
|
|
isize target_index = escape_push_target(ea, stmt, EscapeTarget_Switch);
|
|
|
|
EscapeValue tag = escape_value();
|
|
EscapeValue tag_ptr = escape_value();
|
|
bool tag_by_ptr = false;
|
|
if (type_switch_rhs != nullptr) {
|
|
Type *t = type_switch_rhs->tav.type;
|
|
|
|
tag_by_ptr = is_type_any(t) || is_type_pointer(t);
|
|
tag = escape_expr(ea, type_switch_rhs);
|
|
tag_ptr = escape_variant_addr(ea, type_switch_rhs);
|
|
}
|
|
|
|
EscapeState entry = escape_state_clone(ea->state);
|
|
EscapeState exit = escape_state_unreachable();
|
|
|
|
bool has_default = false;
|
|
ea->fallthrough = escape_state_unreachable();
|
|
|
|
ast_node(bs, BlockStmt, body);
|
|
for (Ast *clause : bs->stmts) {
|
|
if (clause->kind != Ast_CaseClause) {
|
|
continue;
|
|
}
|
|
ast_node(cc, CaseClause, clause);
|
|
if (cc->list.count == 0) {
|
|
has_default = true;
|
|
}
|
|
ea->state = escape_state_clone(entry);
|
|
escape_state_join(&ea->state, ea->fallthrough);
|
|
ea->fallthrough = escape_state_unreachable();
|
|
for (Ast *e : cc->list) {
|
|
escape_visit_exits(ea, e);
|
|
}
|
|
isize alias_count = ea->aliases.count;
|
|
if (cc->implicit_entity != nullptr) {
|
|
Entity *e = cc->implicit_entity;
|
|
if (e->flags & EntityFlag_Value) {
|
|
EscapeValue v = escape_value();
|
|
if (tag_by_ptr) {
|
|
v = escape_load(ea, tag_ptr, e->type);
|
|
} else {
|
|
for (EscapeValueFact const &f : tag) {
|
|
escape_value_add(&v, {}, f.origin);
|
|
}
|
|
}
|
|
escape_store_obj(ea, {e}, {}, v, EscapeUpdate_Replace);
|
|
} else {
|
|
for (EscapeValueFact const &f : tag_ptr) {
|
|
array_add(&ea->aliases, EscapeAlias{e, f.origin});
|
|
}
|
|
}
|
|
}
|
|
escape_block(ea, cc->stmts);
|
|
ea->aliases.count = alias_count;
|
|
escape_forget_entity(ea, cc->implicit_entity);
|
|
escape_state_join(&exit, ea->state);
|
|
}
|
|
if (!has_default && !exhaustive) {
|
|
escape_state_join(&exit, entry);
|
|
}
|
|
escape_state_join(&exit, ea->targets[target_index].breaks);
|
|
array_pop(&ea->targets);
|
|
ea->state = exit;
|
|
}
|
|
|
|
gb_internal void escape_stmt(EscapeAnalysis *ea, Ast *node) {
|
|
if (node == nullptr || !ea->state.reachable) {
|
|
return;
|
|
}
|
|
switch (node->kind) {
|
|
case_ast_node(es, ExprStmt, node);
|
|
escape_visit_exits(ea, es->expr);
|
|
if (is_diverging_stmt(node)) {
|
|
ea->state.reachable = false;
|
|
}
|
|
case_end;
|
|
|
|
case_ast_node(vd, ValueDecl, node);
|
|
if (!vd->is_mutable) {
|
|
break;
|
|
}
|
|
if (vd->values.count == vd->names.count) {
|
|
for_array(i, vd->names) {
|
|
Entity *e = entity_of_node(vd->names[i]);
|
|
if (!escape_is_local(e) || escape_type_is_plain(e->type)) {
|
|
escape_visit_exits(ea, vd->values[i]);
|
|
continue;
|
|
}
|
|
EscapeValue v = escape_expr(ea, vd->values[i]);
|
|
escape_store_obj(ea, {e}, {}, escape_convert(ea, v, vd->values[i], e->type), EscapeUpdate_Replace);
|
|
}
|
|
} else {
|
|
Array<EscapeValue> results = {};
|
|
Ast *call = nullptr;
|
|
if (vd->values.count == 1) {
|
|
call = escape_call_of(vd->values[0]);
|
|
}
|
|
if (call != nullptr) {
|
|
results = escape_call(ea, call);
|
|
} else {
|
|
for (Ast *value : vd->values) {
|
|
escape_visit_exits(ea, value);
|
|
}
|
|
}
|
|
for_array(i, vd->names) {
|
|
Entity *e = entity_of_node(vd->names[i]);
|
|
if (!escape_is_local(e)) {
|
|
continue;
|
|
}
|
|
EscapeValue v = escape_value();
|
|
if (i < results.count) {
|
|
v = results[i];
|
|
}
|
|
escape_store_obj(ea, {e}, {}, v, EscapeUpdate_Replace);
|
|
}
|
|
}
|
|
case_end;
|
|
|
|
case_ast_node(as, AssignStmt, node);
|
|
if (as->op.kind != Token_Eq) {
|
|
for (Ast *lhs : as->lhs) {
|
|
escape_visit_exits(ea, lhs);
|
|
}
|
|
for (Ast *rhs : as->rhs) {
|
|
escape_visit_exits(ea, rhs);
|
|
}
|
|
break;
|
|
}
|
|
if (as->lhs.count == as->rhs.count) {
|
|
// every value is evaluated before any is stored, as in `a, b = b, a`
|
|
auto values = array_make<EscapeValue>(temporary_allocator(), as->rhs.count);
|
|
auto stored = array_make<bool>(temporary_allocator(), as->lhs.count);
|
|
for_array(i, as->rhs) {
|
|
Ast *lhs = unparen_expr(as->lhs[i]);
|
|
stored[i] = !escape_type_is_plain(lhs->tav.type);
|
|
if (lhs->kind == Ast_SelectorExpr && is_type_raw_union(type_deref(lhs->SelectorExpr.expr->tav.type))) {
|
|
// a plain value stored into a raw union still overwrites its pointers
|
|
stored[i] = true;
|
|
}
|
|
if (stored[i]) {
|
|
values[i] = escape_convert(ea, escape_expr(ea, as->rhs[i]), as->rhs[i], lhs->tav.type);
|
|
} else {
|
|
escape_visit_exits(ea, as->rhs[i]);
|
|
}
|
|
}
|
|
for_array(i, as->lhs) {
|
|
if (stored[i]) {
|
|
escape_store(ea, as->lhs[i], values[i]);
|
|
}
|
|
}
|
|
} else {
|
|
Array<EscapeValue> results = {};
|
|
Ast *call = nullptr;
|
|
if (as->rhs.count == 1) {
|
|
call = escape_call_of(as->rhs[0]);
|
|
}
|
|
if (call != nullptr) {
|
|
results = escape_call(ea, call);
|
|
} else {
|
|
for (Ast *rhs : as->rhs) {
|
|
escape_visit_exits(ea, rhs);
|
|
}
|
|
}
|
|
for_array(i, as->lhs) {
|
|
EscapeValue v = escape_value();
|
|
if (i < results.count) {
|
|
v = results[i];
|
|
}
|
|
escape_store(ea, as->lhs[i], v);
|
|
}
|
|
}
|
|
case_end;
|
|
|
|
case_ast_node(bs, BlockStmt, node);
|
|
if (bs->label != nullptr) {
|
|
isize target_index = escape_push_target(ea, node, EscapeTarget_Labelled);
|
|
escape_block(ea, bs->stmts);
|
|
escape_state_join(&ea->state, ea->targets[target_index].breaks);
|
|
array_pop(&ea->targets);
|
|
} else {
|
|
escape_block(ea, bs->stmts);
|
|
}
|
|
case_end;
|
|
|
|
case_ast_node(is, IfStmt, node);
|
|
isize target_index = -1;
|
|
if (is->label != nullptr) {
|
|
target_index = escape_push_target(ea, node, EscapeTarget_Labelled);
|
|
}
|
|
|
|
escape_stmt(ea, is->init);
|
|
escape_visit_exits(ea, is->cond);
|
|
|
|
EscapeState other = escape_state_clone(ea->state);
|
|
escape_stmt(ea, is->body);
|
|
|
|
EscapeState then = ea->state;
|
|
ea->state = other;
|
|
escape_stmt(ea, is->else_stmt);
|
|
|
|
escape_state_join(&ea->state, then);
|
|
|
|
if (target_index >= 0) {
|
|
escape_state_join(&ea->state, ea->targets[target_index].breaks);
|
|
array_pop(&ea->targets);
|
|
}
|
|
|
|
escape_forget(ea, is->init);
|
|
case_end;
|
|
|
|
case_ast_node(ws, WhenStmt, node);
|
|
if (!ws->is_cond_determined) {
|
|
break;
|
|
}
|
|
if (ws->determined_cond) {
|
|
escape_stmt(ea, ws->body);
|
|
} else {
|
|
escape_stmt(ea, ws->else_stmt);
|
|
}
|
|
case_end;
|
|
|
|
case_ast_node(rs, ReturnStmt, node);
|
|
isize result_count = 0;
|
|
if (ea->pt->results != nullptr) {
|
|
result_count = ea->pt->results->Tuple.variables.count;
|
|
}
|
|
auto values = array_make<EscapeValue>(temporary_allocator(), 0, result_count);
|
|
|
|
if (rs->results.count != result_count) {
|
|
// a bare return of named results, or the results of a single call
|
|
Ast *call = nullptr;
|
|
if (rs->results.count == 1) {
|
|
call = escape_call_of(rs->results[0]);
|
|
}
|
|
if (call == nullptr) {
|
|
for (Ast *expr : rs->results) {
|
|
escape_visit_exits(ea, expr);
|
|
}
|
|
escape_exit(ea, node, {}, values);
|
|
break;
|
|
}
|
|
|
|
Array<EscapeValue> results = escape_call(ea, call);
|
|
for (isize i = 0; i < result_count; i++) {
|
|
EscapeValue v = escape_value();
|
|
if (i < results.count) {
|
|
v = results[i];
|
|
}
|
|
array_add(&values, v);
|
|
}
|
|
escape_exit(ea, node, rs->results, values);
|
|
break;
|
|
}
|
|
|
|
for_array(i, rs->results) {
|
|
Ast *expr = rs->results[i];
|
|
Entity *e = ea->pt->results->Tuple.variables[i];
|
|
if (escape_type_is_plain(e->type)) {
|
|
escape_visit_exits(ea, expr);
|
|
array_add(&values, escape_value());
|
|
continue;
|
|
}
|
|
EscapeValue v = escape_expr(ea, expr);
|
|
array_add(&values, escape_convert(ea, v, expr, e->type));
|
|
}
|
|
|
|
escape_exit(ea, node, rs->results, values);
|
|
case_end;
|
|
|
|
case_ast_node(fs, ForStmt, node);
|
|
escape_stmt(ea, fs->init);
|
|
escape_loop(ea, node, fs->cond, fs->post, fs->body, nullptr, nullptr, nullptr);
|
|
escape_forget(ea, fs->init);
|
|
case_end;
|
|
|
|
case_ast_node(rs, RangeStmt, node);
|
|
Ast *val0 = nullptr;
|
|
Ast *val1 = nullptr;
|
|
if (rs->vals.count > 0) {
|
|
val0 = rs->vals[0];
|
|
}
|
|
if (rs->vals.count > 1) {
|
|
val1 = rs->vals[1];
|
|
}
|
|
escape_loop(ea, node, nullptr, nullptr, rs->body, val0, val1, rs->expr);
|
|
case_end;
|
|
|
|
case_ast_node(rs, UnrollRangeStmt, node);
|
|
escape_loop(ea, node, nullptr, nullptr, rs->body, rs->val0, rs->val1, rs->expr);
|
|
case_end;
|
|
|
|
case_ast_node(ss, SwitchStmt, node);
|
|
escape_stmt(ea, ss->init);
|
|
escape_visit_exits(ea, ss->tag);
|
|
bool exhaustive = false;
|
|
if (!ss->partial && ss->tag != nullptr) {
|
|
// a switch on an enum must handle every value
|
|
exhaustive = is_type_enum(ss->tag->tav.type);
|
|
}
|
|
escape_switch(ea, node, ss->body, nullptr, exhaustive);
|
|
escape_forget(ea, ss->init);
|
|
case_end;
|
|
|
|
case_ast_node(ss, TypeSwitchStmt, node);
|
|
Ast *rhs = nullptr;
|
|
if (ss->tag != nullptr && ss->tag->kind == Ast_AssignStmt && ss->tag->AssignStmt.rhs.count == 1) {
|
|
rhs = ss->tag->AssignStmt.rhs[0];
|
|
}
|
|
if (rhs == nullptr) {
|
|
break;
|
|
}
|
|
escape_switch(ea, node, ss->body, rhs, !ss->partial);
|
|
case_end;
|
|
|
|
case_ast_node(ds, DeferStmt, node);
|
|
if (ea->defers.count > 0) {
|
|
array_add(&ea->defers[ea->defers.count-1], ds->stmt);
|
|
}
|
|
case_end;
|
|
|
|
case_ast_node(bs, BranchStmt, node);
|
|
if (bs->token.kind == Token_fallthrough) {
|
|
ea->fallthrough = escape_state_clone(ea->state);
|
|
ea->state.reachable = false;
|
|
} else {
|
|
escape_branch(ea, bs->label, bs->token.kind);
|
|
}
|
|
case_end;
|
|
}
|
|
}
|
|
|
|
|
|
// 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> 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
|
|
Array<i32> caller_offsets; // group -> the groups which may call it, as `callers[caller_offsets[gi]..<caller_offsets[gi+1]]`
|
|
Array<i32> callers;
|
|
std::atomic<i32> * pending; // per group the groups it may call 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};
|
|
}
|
|
|
|
// `reports` keeps what it reports rather than reporting it, when not null
|
|
gb_internal Slice<EscapeFlow> escape_analyse(EscapeGraph *g, i32 v, Array<EscapeReport> *reports) {
|
|
ProcInfo *pi = g->procs[v];
|
|
Type *type = pi->type;
|
|
Ast *body = pi->body;
|
|
if (type == nullptr || type->kind != Type_Proc || body == nullptr || body->kind != Ast_BlockStmt) {
|
|
return {};
|
|
}
|
|
TEMPORARY_ALLOCATOR_GUARD();
|
|
|
|
EscapeAnalysis ea = {};
|
|
ea.pt = &type->Proc;
|
|
ea.state = escape_state_unreachable();
|
|
ea.state.reachable = true;
|
|
|
|
ea.defers = array_make<Array<Ast *> >(temporary_allocator());
|
|
ea.targets = array_make<EscapeTarget>(temporary_allocator());
|
|
ea.aliases = array_make<EscapeAlias>(temporary_allocator());
|
|
ea.reported = array_make<Ast *>(temporary_allocator());
|
|
ea.flows = array_make<EscapeFlow>(temporary_allocator(), 0, 0);
|
|
ea.graph = g;
|
|
ea.group = g->group_of[v];
|
|
ea.reports = reports;
|
|
|
|
if (ea.pt->variadic &&
|
|
!ea.pt->c_vararg &&
|
|
ea.pt->params != nullptr &&
|
|
ea.pt->variadic_index < ea.pt->params->Tuple.variables.count) {
|
|
ea.variadic = ea.pt->params->Tuple.variables[ea.pt->variadic_index];
|
|
}
|
|
|
|
if (build_context.build_mode == BuildMode_Executable &&
|
|
!build_context.no_entry_point &&
|
|
build_context.command_kind != Command_test) {
|
|
Entity *e = pi->decl->entity.load();
|
|
ea.muted = e != nullptr && e == global_checker_ptr.load(std::memory_order_relaxed)->info.entry_point;
|
|
}
|
|
|
|
ErrorInstantiations prev_instantiations = global_error_context.instantiations;
|
|
global_error_context.instantiations = escape_instantiations_of(pi);
|
|
escape_stmt(&ea, body);
|
|
if (ea.state.reachable) {
|
|
escape_exit(&ea, body, {}, {});
|
|
}
|
|
global_error_context.instantiations = prev_instantiations;
|
|
|
|
// the callers read it once this analysis and its temporary memory are gone
|
|
auto flows = permanent_slice_make<EscapeFlow>(ea.flows.count);
|
|
for_array(i, ea.flows) {
|
|
flows[i] = ea.flows[i];
|
|
flows[i].target_path = slice_clone(permanent_allocator(), ea.flows[i].target_path);
|
|
flows[i].param_path = slice_clone(permanent_allocator(), ea.flows[i].param_path);
|
|
}
|
|
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
|
|
bool changed;
|
|
Slice<EscapeFlow> flows;
|
|
Array<EscapeReport> reports; // of its latest analysis
|
|
};
|
|
|
|
gb_internal WORKER_TASK_PROC(escape_member_worker) {
|
|
EscapeMemberTask *t = cast(EscapeMemberTask *)data;
|
|
array_clear(&t->reports);
|
|
t->flows = escape_analyse(t->graph, t->v, &t->reports);
|
|
return 0;
|
|
}
|
|
|
|
// each stale one is analysed with the flows the others had before, so the order they are analysed in does not matter
|
|
gb_internal void escape_analyse_members(EscapeGraph *g, Slice<EscapeMemberTask> tasks) {
|
|
TaskGroup group = {};
|
|
for (EscapeMemberTask &t : tasks) {
|
|
if (!t.stale) {
|
|
continue;
|
|
}
|
|
if (g->threaded) {
|
|
thread_pool_add_task(&group, escape_member_worker, &t);
|
|
} else {
|
|
escape_member_worker(&t);
|
|
}
|
|
}
|
|
thread_pool_wait(&group);
|
|
}
|
|
|
|
gb_internal void escape_analyse_group(EscapeGraph *g, i32 gi) {
|
|
enum : isize { MAX_ITERATION_COUNT = 16 };
|
|
|
|
i32 start = g->group_offsets[gi];
|
|
i32 end = g->group_offsets[gi+1];
|
|
|
|
i32 first = g->members[start];
|
|
bool recursive = end - start > 1;
|
|
for (i32 i = g->offsets[first]; i < g->offsets[first+1]; i++) {
|
|
recursive |= g->targets[i] == first;
|
|
}
|
|
if (!recursive) {
|
|
DeclInfo *d = g->procs[first]->decl;
|
|
d->escape_flows = escape_analyse(g, first, nullptr);
|
|
d->escapes_analysed.store(true);
|
|
return;
|
|
}
|
|
|
|
auto tasks = slice_make<EscapeMemberTask>(heap_allocator(), end - start);
|
|
defer (slice_free(&tasks, heap_allocator()));
|
|
for (i32 k = start; k < end; k++) {
|
|
EscapeMemberTask &t = tasks[k - start];
|
|
t = {g, g->members[k]};
|
|
t.stale = true;
|
|
array_init(&t.reports, heap_allocator(), 0, 0);
|
|
}
|
|
|
|
// what each may return or store depends on the others, so they start from nothing until that settles,
|
|
// analysing again only those calling one whose flows changed, so the latest analysis of each was with what settled
|
|
bool settled = false;
|
|
for (isize iteration = 0; iteration < MAX_ITERATION_COUNT && !settled; iteration++) {
|
|
escape_analyse_members(g, tasks);
|
|
settled = true;
|
|
for (EscapeMemberTask &t : tasks) {
|
|
t.changed = false;
|
|
if (t.stale) {
|
|
DeclInfo *d = g->procs[t.v]->decl;
|
|
t.changed = !escape_flows_eq(t.flows, d->escape_flows);
|
|
d->escape_flows = t.flows;
|
|
settled &= !t.changed;
|
|
}
|
|
}
|
|
|
|
for (EscapeMemberTask &t : tasks) {
|
|
t.stale = false;
|
|
for (i32 i = g->offsets[t.v]; i < g->offsets[t.v+1]; i++) {
|
|
for (EscapeMemberTask const &callee : tasks) {
|
|
t.stale |= callee.v == g->targets[i] && callee.changed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!settled) {
|
|
for (EscapeMemberTask &t : tasks) {
|
|
t.stale = true;
|
|
}
|
|
escape_analyse_members(g, tasks);
|
|
}
|
|
|
|
for (EscapeMemberTask &t : tasks) {
|
|
ProcInfo *pi = g->procs[t.v];
|
|
ErrorInstantiations prev_instantiations = global_error_context.instantiations;
|
|
global_error_context.instantiations = escape_instantiations_of(pi);
|
|
for (EscapeReport const &r : t.reports) {
|
|
escape_report_emit(r);
|
|
}
|
|
global_error_context.instantiations = prev_instantiations;
|
|
array_free(&t.reports);
|
|
|
|
pi->decl->escape_flows = t.flows;
|
|
pi->decl->escapes_analysed.store(true);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
// e.g. in a body which failed to check, or to a foreign procedure
|
|
return false;
|
|
}
|
|
*caller = call.caller->escape_index;
|
|
*callee = d->escape_index;
|
|
return true;
|
|
}
|
|
|
|
gb_internal void escape_graph_count_calls(CheckedCall *calls, isize count) {
|
|
EscapeGraph *g = &escape_graph;
|
|
for (isize i = 0; i < count; i++) {
|
|
i32 caller = 0;
|
|
i32 callee = 0;
|
|
if (escape_call_edge(calls[i], &caller, &callee)) {
|
|
g->cursors[caller].fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
}
|
|
}
|
|
|
|
gb_internal void escape_graph_place_calls(CheckedCall *calls, isize count) {
|
|
EscapeGraph *g = &escape_graph;
|
|
for (isize i = 0; i < count; i++) {
|
|
i32 caller = 0;
|
|
i32 callee = 0;
|
|
if (escape_call_edge(calls[i], &caller, &callee)) {
|
|
g->targets[g->cursors[caller].fetch_add(1, std::memory_order_relaxed)] = callee;
|
|
}
|
|
}
|
|
}
|
|
|
|
gb_internal WORKER_TASK_PROC(escape_group_worker) {
|
|
EscapeGraph *g = &escape_graph;
|
|
i32 gi = cast(i32)cast(intptr)data;
|
|
for (;;) {
|
|
escape_analyse_group(g, gi);
|
|
|
|
// carries on with one of the groups this releases, rather than queueing every one of them
|
|
bool carry_on = false;
|
|
i32 next_gi = 0;
|
|
for (i32 i = g->caller_offsets[gi]; i < g->caller_offsets[gi+1]; i++) {
|
|
i32 next = g->callers[i];
|
|
if (g->pending[next].fetch_sub(1) != 1) {
|
|
continue;
|
|
}
|
|
if (carry_on) {
|
|
thread_pool_add_task(escape_group_worker, cast(void *)cast(intptr)next);
|
|
} else {
|
|
carry_on = true;
|
|
next_gi = next;
|
|
}
|
|
}
|
|
if (!carry_on) {
|
|
return 0;
|
|
}
|
|
gi = next_gi;
|
|
}
|
|
}
|
|
|
|
// once every body is checked, as a call needs to know what its procedure does
|
|
gb_internal void check_escapes(Checker *c) {
|
|
EscapeGraph *g = &escape_graph;
|
|
defer ({
|
|
array_free(&g->procs);
|
|
array_free(&g->offsets);
|
|
array_free(&g->targets);
|
|
array_free(&g->group_of);
|
|
array_free(&g->group_offsets);
|
|
array_free(&g->members);
|
|
array_free(&g->caller_offsets);
|
|
array_free(&g->callers);
|
|
gb_free(heap_allocator(), g->pending);
|
|
g->pending = nullptr;
|
|
});
|
|
array_init(&g->procs, heap_allocator());
|
|
per_thread_array_gather(&c->info.checked_bodies_queue, &g->procs);
|
|
i32 count = cast(i32)g->procs.count;
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
for (i32 v = 0; v < count; v++) {
|
|
g->procs[v]->decl->escape_index = v;
|
|
}
|
|
|
|
g->threaded = !build_context.no_threaded_checker && build_context.thread_count > 1;
|
|
|
|
{
|
|
auto calls = array_make<CheckedCall>(heap_allocator());
|
|
defer (array_free(&calls));
|
|
per_thread_array_gather(&c->info.checked_calls_queue, &calls);
|
|
|
|
// 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);
|
|
defer (gb_free(heap_allocator(), g->cursors));
|
|
for (i32 v = 0; v < count; v++) {
|
|
g->cursors[v].store(0, std::memory_order_relaxed);
|
|
}
|
|
if (g->threaded) {
|
|
thread_pool_for_chunks(calls.data, calls.count, 4096, escape_graph_count_calls);
|
|
} else {
|
|
escape_graph_count_calls(calls.data, calls.count);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
array_init(&g->targets, heap_allocator(), g->offsets[count]);
|
|
if (g->threaded) {
|
|
thread_pool_for_chunks(calls.data, calls.count, 4096, escape_graph_place_calls);
|
|
} else {
|
|
escape_graph_place_calls(calls.data, calls.count);
|
|
}
|
|
}
|
|
|
|
array_init(&g->group_of, heap_allocator(), count);
|
|
i32 group_count = global_graph_scc(count, g->offsets, g->targets, &g->group_of);
|
|
{
|
|
auto procs = array_make<i32>(heap_allocator(), count);
|
|
defer (array_free(&procs));
|
|
for (i32 v = 0; v < count; v++) {
|
|
procs[v] = v;
|
|
}
|
|
global_graph_csr(group_count, g->group_of, procs, &g->group_offsets, &g->members);
|
|
}
|
|
|
|
if (!g->threaded) {
|
|
for (i32 gi = 0; gi < group_count; gi++) {
|
|
escape_analyse_group(g, gi);
|
|
}
|
|
return;
|
|
}
|
|
|
|
{
|
|
auto edge_from = array_make<i32>(heap_allocator(), 0, group_count);
|
|
auto edge_to = array_make<i32>(heap_allocator(), 0, group_count);
|
|
auto seen = array_make<i32>(heap_allocator(), group_count);
|
|
defer (array_free(&edge_from));
|
|
defer (array_free(&edge_to));
|
|
defer (array_free(&seen));
|
|
|
|
g->pending = gb_alloc_array(heap_allocator(), std::atomic<i32>, group_count);
|
|
for (i32 gi = 0; gi < group_count; gi++) {
|
|
seen[gi] = -1;
|
|
g->pending[gi].store(0);
|
|
}
|
|
for (i32 gi = 0; gi < group_count; gi++) {
|
|
for (i32 k = g->group_offsets[gi]; k < g->group_offsets[gi+1]; k++) {
|
|
i32 v = g->members[k];
|
|
for (i32 i = g->offsets[v]; i < g->offsets[v+1]; i++) {
|
|
i32 callee = g->group_of[g->targets[i]];
|
|
if (callee != gi && seen[callee] != gi) {
|
|
seen[callee] = gi;
|
|
array_add(&edge_from, callee);
|
|
array_add(&edge_to, gi);
|
|
g->pending[gi].fetch_add(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
global_graph_csr(group_count, edge_from, edge_to, &g->caller_offsets, &g->callers);
|
|
}
|
|
|
|
// all found before any is analysed, as analysing one releases others
|
|
auto ready = array_make<i32>(heap_allocator(), 0, group_count);
|
|
defer (array_free(&ready));
|
|
for (i32 gi = 0; gi < group_count; gi++) {
|
|
if (g->pending[gi].load() == 0) {
|
|
array_add(&ready, gi);
|
|
}
|
|
}
|
|
for (i32 gi : ready) {
|
|
thread_pool_add_task(escape_group_worker, cast(void *)cast(intptr)gi);
|
|
}
|
|
thread_pool_wait();
|
|
}
|