Files
Odin/src/checker.hpp
T
Lucas Perlind 5c73b4ef58 Add attribute @(no_sanitize_address)
The purposes of this attribute is to let procedures opt-out of being
instrumented with asan. Typically an allocator that includes 'in-band'
meta-data will be accessing poisoned values (such as tlsf).

Making asan work with these allocators becomes very challenging so
just being to ignore asan within specific allocator procedures
makes it easier to reason and removes the need to temporarily
poison and unpoison allocator data.
2025-05-01 20:42:21 +10:00

634 lines
16 KiB
C++

// checker.hpp
struct Type;
struct Entity;
struct Scope;
struct DeclInfo;
struct AstFile;
struct Checker;
struct CheckerInfo;
struct CheckerContext;
enum AddressingMode : u8;
struct TypeAndValue;
// ExprInfo stores information used for "untyped" expressions
struct ExprInfo {
AddressingMode mode;
bool is_lhs; // Debug info
Type * type;
ExactValue value;
};
gb_internal gb_inline ExprInfo *make_expr_info(AddressingMode mode, Type *type, ExactValue const &value, bool is_lhs) {
ExprInfo *ei = gb_alloc_item(permanent_allocator(), ExprInfo);
ei->mode = mode;
ei->type = type;
ei->value = value;
ei->is_lhs = is_lhs;
return ei;
}
enum ExprKind {
Expr_Expr,
Expr_Stmt,
};
// Statements and Declarations
enum StmtFlag {
Stmt_BreakAllowed = 1<<0,
Stmt_ContinueAllowed = 1<<1,
Stmt_FallthroughAllowed = 1<<2,
Stmt_TypeSwitch = 1<<4,
Stmt_CheckScopeDecls = 1<<5,
};
enum BuiltinProcPkg {
BuiltinProcPkg_builtin,
BuiltinProcPkg_intrinsics,
BuiltinProcPkg_COUNT
};
String builtin_proc_pkg_name[BuiltinProcPkg_COUNT] = {
str_lit("builtin"),
str_lit("intrinsics"),
};
struct BuiltinProc {
String name;
isize arg_count;
bool variadic;
ExprKind kind;
BuiltinProcPkg pkg;
bool diverging;
bool ignore_results; // ignores require results handling
};
#include "checker_builtin_procs.hpp"
// Operand is used as an intermediate value whilst checking
// Operands store an addressing mode, the expression being evaluated,
// its type and node, and other specific information for certain
// addressing modes
// Its zero-value is a valid "invalid operand"
struct Operand {
AddressingMode mode;
Type * type;
ExactValue value;
Ast * expr;
BuiltinProcId builtin_id;
Entity * proc_group;
};
struct BlockLabel {
String name;
Ast *label; // Ast_Label;
};
enum DeferredProcedureKind {
DeferredProcedure_none,
DeferredProcedure_in,
DeferredProcedure_out,
DeferredProcedure_in_out,
DeferredProcedure_in_by_ptr,
DeferredProcedure_out_by_ptr,
DeferredProcedure_in_out_by_ptr,
};
struct DeferredProcedure {
DeferredProcedureKind kind;
Entity *entity;
};
enum InstrumentationFlag : i32 {
Instrumentation_Enabled = -1,
Instrumentation_Default = 0,
Instrumentation_Disabled = +1,
};
struct AttributeContext {
String link_name;
String link_prefix;
String link_suffix;
String link_section;
String linkage;
isize init_expr_list_count;
String thread_local_model;
String deprecated_message;
String warning_message;
DeferredProcedure deferred_procedure;
bool is_export : 1;
bool is_static : 1;
bool require_results : 1;
bool require_declaration : 1;
bool has_disabled_proc : 1;
bool disabled_proc : 1;
bool test : 1;
bool init : 1;
bool fini : 1;
bool set_cold : 1;
bool entry_point_only : 1;
bool instrumentation_enter : 1;
bool instrumentation_exit : 1;
bool no_sanitize_address : 1;
bool rodata : 1;
bool ignore_duplicates : 1;
u32 optimization_mode; // ProcedureOptimizationMode
i64 foreign_import_priority_index;
String extra_linker_flags;
InstrumentationFlag no_instrumentation;
String objc_class;
String objc_name;
bool objc_is_class_method;
Type * objc_type;
String require_target_feature; // required by the target micro-architecture
String enable_target_feature; // will be enabled for the procedure only
};
gb_internal gb_inline AttributeContext make_attribute_context(String link_prefix, String link_suffix) {
AttributeContext ac = {};
ac.link_prefix = link_prefix;
ac.link_suffix = link_suffix;
return ac;
}
#define DECL_ATTRIBUTE_PROC(_name) bool _name(CheckerContext *c, Ast *elem, String name, Ast *value, AttributeContext *ac)
typedef DECL_ATTRIBUTE_PROC(DeclAttributeProc);
gb_internal void check_decl_attributes(CheckerContext *c, Array<Ast *> const &attributes, DeclAttributeProc *proc, AttributeContext *ac);
#include "name_canonicalization.hpp"
enum ProcCheckedState : u8 {
ProcCheckedState_Unchecked,
ProcCheckedState_InProgress,
ProcCheckedState_Checked,
ProcCheckedState_COUNT
};
char const *ProcCheckedState_strings[ProcCheckedState_COUNT] {
"Unchecked",
"In Progress",
"Checked",
};
struct VariadicReuseData {
Type *slice_type; // ..elem_type
i64 max_count;
};
// DeclInfo is used to store information of certain declarations to allow for "any order" usage
struct DeclInfo {
DeclInfo * parent; // NOTE(bill): only used for procedure literals at the moment
BlockingMutex next_mutex;
DeclInfo * next_child;
DeclInfo * next_sibling;
Scope * scope;
Entity *entity;
Ast * decl_node;
Ast * type_expr;
Ast * init_expr;
Array<Ast *> attributes;
Ast * proc_lit; // Ast_ProcLit
Type * gen_proc_type; // Precalculated
bool is_using;
bool where_clauses_evaluated;
bool foreign_require_results;
std::atomic<ProcCheckedState> proc_checked_state;
BlockingMutex proc_checked_mutex;
isize defer_used;
bool defer_use_checked;
CommentGroup *comment;
CommentGroup *docs;
RwMutex deps_mutex;
PtrSet<Entity *> deps;
RwMutex type_info_deps_mutex;
TypeSet type_info_deps;
BlockingMutex type_and_value_mutex;
Array<BlockLabel> labels;
i32 scope_index;
Array<VariadicReuseData> variadic_reuses;
i64 variadic_reuse_max_bytes;
i64 variadic_reuse_max_align;
// NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time
struct lbModule *code_gen_module;
};
// ProcInfo stores the information needed for checking a procedure
struct ProcInfo {
AstFile * file;
Token token;
DeclInfo *decl;
Type * type; // Type_Procedure
Ast * body; // Ast_BlockStmt
u64 tags;
bool generated_from_polymorphic;
Ast * poly_def_node;
};
enum ScopeFlag : i32 {
ScopeFlag_Pkg = 1<<1,
ScopeFlag_Builtin = 1<<2,
ScopeFlag_Global = 1<<3,
ScopeFlag_File = 1<<4,
ScopeFlag_Init = 1<<5,
ScopeFlag_Proc = 1<<6,
ScopeFlag_Type = 1<<7,
ScopeFlag_HasBeenImported = 1<<10, // This is only applicable to file scopes
ScopeFlag_ContextDefined = 1<<16,
};
enum { DEFAULT_SCOPE_CAPACITY = 32 };
struct Scope {
Ast * node;
Scope * parent;
std::atomic<Scope *> next;
std::atomic<Scope *> head_child;
i32 index; // within a procedure
RwMutex mutex;
StringMap<Entity *> elements;
PtrSet<Scope *> imported;
DeclInfo *decl_info;
i32 flags; // ScopeFlag
union {
AstPackage *pkg;
AstFile * file;
Entity * procedure_entity;
};
};
struct EntityGraphNode;
typedef PtrSet<EntityGraphNode *> EntityGraphNodeSet;
struct EntityGraphNode {
Entity * entity; // Procedure, Variable, Constant
EntityGraphNodeSet pred;
EntityGraphNodeSet succ;
isize index; // Index in array/queue
isize dep_count;
};
struct ImportGraphNode;
typedef PtrSet<ImportGraphNode *> ImportGraphNodeSet;
struct ImportGraphNode {
AstPackage * pkg;
Scope * scope;
ImportGraphNodeSet pred;
ImportGraphNodeSet succ;
isize index; // Index in array/queue
isize dep_count;
};
enum EntityVisiblityKind {
EntityVisiblity_Public,
EntityVisiblity_PrivateToPackage,
EntityVisiblity_PrivateToFile,
};
struct ForeignContext {
Ast * curr_library;
ProcCallingConvention default_cc;
String link_prefix;
String link_suffix;
EntityVisiblityKind visibility_kind;
bool require_results;
};
typedef Array<Entity *> CheckerTypePath;
typedef Array<Type *> CheckerPolyPath;
struct AtomOpMapEntry {
u32 kind;
Ast *node;
};
struct CheckerContext;
struct UntypedExprInfo {
Ast *expr;
ExprInfo *info;
};
typedef PtrMap<Ast *, ExprInfo *> UntypedExprInfoMap;
enum ObjcMsgKind : u32 {
ObjcMsg_normal,
ObjcMsg_fpret,
ObjcMsg_fp2ret,
ObjcMsg_stret,
};
struct ObjcMsgData {
ObjcMsgKind kind;
Type *proc_type;
};
enum LoadFileTier {
LoadFileTier_Invalid,
LoadFileTier_Exists,
LoadFileTier_Contents,
};
struct LoadFileCache {
LoadFileTier tier;
bool exists;
String path;
gbFileError file_error;
String data;
StringMap<u64> hashes;
};
struct LoadDirectoryFile {
String file_name;
String data;
};
struct LoadDirectoryCache {
String path;
gbFileError file_error;
Array<LoadFileCache *> files;
};
struct GenProcsData {
Array<Entity *> procs;
RwMutex mutex;
};
struct GenTypesData {
Array<Entity *> types;
RecursiveMutex mutex;
};
struct Defineable {
String name;
ExactValue default_value;
TokenPos pos;
CommentGroup *docs;
// These strings are only computed from previous fields when defineables are being shown or exported.
String default_value_str;
String pos_str;
};
// CheckerInfo stores all the symbol information for a type-checked program
struct CheckerInfo {
Checker *checker;
StringMap<AstFile *> files; // Key (full path)
StringMap<AstPackage *> packages; // Key (full path)
Array<DeclInfo *> variable_init_order;
AstPackage * builtin_package;
AstPackage * runtime_package;
AstPackage * init_package;
Scope * init_scope;
Entity * entry_point;
PtrSet<Entity *> minimum_dependency_set;
BlockingMutex minimum_dependency_type_info_mutex;
PtrMap</*type info hash*/u64, /*min dep index*/isize> min_dep_type_info_index_map;
TypeSet min_dep_type_info_set;
Array<TypeInfoPair> type_info_types_hash_map; // 2 * type_info_types.count
Array<Entity *> testing_procedures;
Array<Entity *> init_procedures;
Array<Entity *> fini_procedures;
Array<Entity *> definitions;
Array<Entity *> entities;
Array<Entity *> required_foreign_imports_through_force;
BlockingMutex defineables_mutex;
Array<Defineable> defineables;
// Below are accessed within procedures
RwMutex global_untyped_mutex;
UntypedExprInfoMap global_untyped; // NOTE(bill): This needs to be a map and not on the Ast
// as it needs to be iterated across afterwards
BlockingMutex builtin_mutex;
BlockingMutex type_and_value_mutex;
RecursiveMutex lazy_mutex; // Mutex required for lazy type checking of specific files
BlockingMutex gen_types_mutex;
PtrMap<Type *, GenTypesData *> gen_types;
// BlockingMutex type_info_mutex; // NOT recursive
// Array<TypeInfoPair> type_info_types;
// PtrMap<Type *, isize> type_info_map;
// TypeSet type_info_set;
BlockingMutex foreign_mutex; // NOT recursive
StringMap<Entity *> foreigns;
MPSCQueue<Entity *> definition_queue;
MPSCQueue<Entity *> entity_queue;
MPSCQueue<Entity *> required_global_variable_queue;
MPSCQueue<Entity *> required_foreign_imports_through_force_queue;
MPSCQueue<Entity *> foreign_imports_to_check_fullpaths;
MPSCQueue<Entity *> foreign_decls_to_check;
MPSCQueue<Ast *> intrinsics_entry_point_usage;
BlockingMutex objc_types_mutex;
PtrMap<Ast *, ObjcMsgData> objc_msgSend_types;
BlockingMutex load_file_mutex;
StringMap<LoadFileCache *> load_file_cache;
BlockingMutex all_procedures_mutex;
Array<ProcInfo *> all_procedures;
BlockingMutex instrumentation_mutex;
Entity *instrumentation_enter_entity;
Entity *instrumentation_exit_entity;
BlockingMutex load_directory_mutex;
StringMap<LoadDirectoryCache *> load_directory_cache;
PtrMap<Ast *, LoadDirectoryCache *> load_directory_map; // Key: Ast_CallExpr *
};
struct CheckerContext {
// Order matters here
BlockingMutex mutex;
Checker * checker;
CheckerInfo * info;
AstPackage * pkg;
AstFile * file;
Scope * scope;
DeclInfo * decl;
// Order doesn't matter after this
u32 state_flags;
bool in_defer;
Type * type_hint;
Ast * type_hint_expr;
String proc_name;
DeclInfo * curr_proc_decl;
Type * curr_proc_sig;
ProcCallingConvention curr_proc_calling_convention;
bool in_proc_sig;
ForeignContext foreign_context;
CheckerTypePath *type_path;
isize type_level;
UntypedExprInfoMap *untyped;
#define MAX_INLINE_FOR_DEPTH 1024ll
i64 inline_for_depth;
u32 stmt_flags;
bool in_enum_type;
bool collect_delayed_decls;
bool allow_polymorphic_types;
bool disallow_polymorphic_return_types; // NOTE(zen3ger): no poly type decl in return types
bool no_polymorphic_errors;
bool hide_polymorphic_errors;
bool in_polymorphic_specialization;
bool allow_arrow_right_selector_expr;
u8 bit_field_bit_size;
Scope * polymorphic_scope;
Ast *assignment_lhs_hint;
};
gb_internal u64 check_vet_flags(CheckerContext *c);
gb_internal u64 check_vet_flags(Ast *node);
struct Checker {
Parser * parser;
CheckerInfo info;
CheckerContext builtin_ctx;
MPSCQueue<Entity *> procs_with_deferred_to_check;
Array<ProcInfo *> procs_to_check;
BlockingMutex nested_proc_lits_mutex;
Array<DeclInfo *> nested_proc_lits;
MPSCQueue<UntypedExprInfo> global_untyped_queue;
MPSCQueue<Type *> soa_types_to_complete;
};
gb_global AstPackage *builtin_pkg = nullptr;
gb_global AstPackage *intrinsics_pkg = nullptr;
gb_global AstPackage *config_pkg = nullptr;
// CheckerInfo API
gb_internal TypeAndValue type_and_value_of_expr (Ast *expr);
gb_internal Type * type_of_expr (Ast *expr);
gb_internal Entity * implicit_entity_of_node(Ast *clause);
gb_internal DeclInfo * decl_info_of_ident (Ast *ident);
gb_internal DeclInfo * decl_info_of_entity (Entity * e);
gb_internal AstFile * ast_file_of_filename (CheckerInfo *i, String filename);
// IMPORTANT: Only to use once checking is done
gb_internal isize type_info_index (CheckerInfo *i, Type *type, bool error_on_failure);
gb_internal isize type_info_index (CheckerInfo *info, TypeInfoPair pair, bool error_on_failure);
// Will return nullptr if not found
gb_internal Entity *entity_of_node(Ast *expr);
gb_internal Entity *scope_lookup_current(Scope *s, String const &name);
gb_internal Entity *scope_lookup (Scope *s, String const &name);
gb_internal void scope_lookup_parent (Scope *s, String const &name, Scope **scope_, Entity **entity_);
gb_internal Entity *scope_insert (Scope *s, Entity *entity);
gb_internal void add_type_and_value (CheckerContext *c, Ast *expression, AddressingMode mode, Type *type, ExactValue const &value);
gb_internal ExprInfo *check_get_expr_info (CheckerContext *c, Ast *expr);
gb_internal void add_untyped (CheckerContext *c, Ast *expression, AddressingMode mode, Type *basic_type, ExactValue const &value);
gb_internal void add_entity_use (CheckerContext *c, Ast *identifier, Entity *entity);
gb_internal void add_implicit_entity (CheckerContext *c, Ast *node, Entity *e);
gb_internal void add_entity_and_decl_info(CheckerContext *c, Ast *identifier, Entity *e, DeclInfo *d, bool is_exported=true);
gb_internal void add_type_info_type (CheckerContext *c, Type *t);
gb_internal void check_add_import_decl(CheckerContext *c, Ast *decl);
gb_internal void check_add_foreign_import_decl(CheckerContext *c, Ast *decl);
gb_internal void check_entity_decl(CheckerContext *c, Entity *e, DeclInfo *d, Type *named_type);
gb_internal void check_const_decl(CheckerContext *c, Entity *e, Ast *type_expr, Ast *init_expr, Type *named_type);
gb_internal void check_type_decl(CheckerContext *c, Entity *e, Ast *type_expr, Type *def);
gb_internal bool check_arity_match(CheckerContext *c, AstValueDecl *vd, bool is_global = false);
gb_internal void check_collect_entities(CheckerContext *c, Slice<Ast *> const &nodes);
gb_internal void check_collect_entities_from_when_stmt(CheckerContext *c, AstWhenStmt *ws);
gb_internal void check_delayed_file_import_entity(CheckerContext *c, Ast *decl);
gb_internal CheckerTypePath *new_checker_type_path();
gb_internal void destroy_checker_type_path(CheckerTypePath *tp);
gb_internal void check_type_path_push(CheckerContext *c, Entity *e);
gb_internal Entity *check_type_path_pop (CheckerContext *c);
gb_internal void init_core_context(Checker *c);
gb_internal void init_mem_allocator(Checker *c);
gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap *untyped);
gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type);
gb_internal void init_map_internal_types(Type *type);