jak-project/goalc/compiler/Env.h
Tyler Wilding d1ece445d4
Dependency graph work - Part 1 - Preliminary work (#3505)
Relates to #1353 

This adds no new functionality or overhead to the compiler, yet. This is
the preliminary work that has:
- added code to the compiler in several spots to flag when something is
used without being properly required/imported/whatever (disabled by
default)
- that was used to generate project wide file dependencies (some
circulars were manually fixed)
- then that graph underwent a transitive reduction and the result was
written to all `jak1` source files.

The next step will be making this actually produce and use a dependency
graph. Some of the reasons why I'm working on this:
- eliminates more `game.gp` boilerplate. This includes the `.gd` files
to some extent (`*-ag` files and `tpage` files will still need to be
handled) this is the point of the new `bundles` form. This should make
it even easier to add a new file into the source tree.
- a build order that is actually informed from something real and
compiler warnings that tell you when you are using something that won't
be available at build time.
- narrows the search space for doing LSP actions -- like searching for
references. Since it would be way too much work to store in the compiler
every location where every symbol/function/etc is used, I have to do
ad-hoc searches. By having a dependency graph i can significantly reduce
that search space.
- opens the doors for common shared code with a legitimate pattern.
Right now jak 2 shares code from the jak 1 folder. This is basically a
hack -- but by having an explicit require syntax, it would be possible
to reference arbitrary file paths, such as a `common` folder.

Some stats:
- Jak 1 has about 2500 edges between files, including transitives
- With transitives reduced at the source code level, each file seems to
have a modest amount of explicit requirements.

Known issues:
- Tracking the location for where `defmacro`s and virtual state
definitions were defined (and therefore the file) is still problematic.
Because those forms are in a macro environment, the reader does not
track them. I'm wondering if a workaround could be to search the
reader's text_db by not just the `goos::Object` but by the text
position. But for the purposes of finishing this work, I just statically
analyzed and searched the code with throwaway python code.
2024-05-12 12:37:59 -04:00

391 lines
13 KiB
C++

#pragma once
/*!
* @file Env.h
* The Env tree. The stores all of the nested scopes/contexts during compilation and also
* manages the memory for stuff generated during compiling.
*/
#include <memory>
#include <string>
#include <vector>
#include "Label.h"
#include "StaticObject.h"
#include "Val.h"
#include "common/goos/Object.h"
#include "common/type_system/TypeSpec.h"
#include "goalc/regalloc/allocator_interface.h"
class FileEnv;
class BlockEnv;
class FunctionEnv;
class SymbolMacroEnv;
class MacroExpandEnv;
class IR;
enum class EnvKind { FILE_ENV, FUNCTION_ENV, SYMBOL_MACRO_ENV, MACRO_EXPAND_ENV, OTHER_ENV };
/*!
* Parent class for Env's
*/
class Env {
public:
explicit Env(EnvKind kind, Env* parent);
virtual std::string print() = 0;
void emit(const goos::Object& form, std::unique_ptr<IR> ir);
virtual RegVal* make_ireg(const TypeSpec& ts, RegClass reg_class);
virtual void constrain_reg(IRegConstraint constraint); // todo, remove!
virtual RegVal* lexical_lookup(goos::Object sym);
virtual BlockEnv* find_block(const std::string& name);
virtual std::unordered_map<std::string, Label>& get_label_map();
RegVal* make_gpr(const TypeSpec& ts);
RegVal* make_fpr(const TypeSpec& ts);
RegVal* make_vfr(const TypeSpec& ts);
virtual ~Env() = default;
Env* parent() { return m_parent; }
template <typename IR_Type, typename... Args>
void emit_ir(const goos::Object& form, Args&&... args) {
emit(form, std::make_unique<IR_Type>(std::forward<Args>(args)...));
}
FileEnv* file_env() { return m_lowest_envs.file_env; }
FunctionEnv* function_env() { return m_lowest_envs.function_env; }
SymbolMacroEnv* symbol_macro_env() { return m_lowest_envs.symbol_macro_env; }
MacroExpandEnv* macro_expand_env() { return m_lowest_envs.macro_expand_env; }
protected:
EnvKind m_kind;
Env* m_parent = nullptr;
// cache of the lowest env of the given type, possibly including ourselves
struct {
FileEnv* file_env = nullptr;
FunctionEnv* function_env = nullptr;
SymbolMacroEnv* symbol_macro_env = nullptr;
MacroExpandEnv* macro_expand_env = nullptr;
} m_lowest_envs;
};
/*!
* The top-level Env. Holds FileEnvs for all files.
*/
class GlobalEnv : public Env {
public:
GlobalEnv();
std::string print() override;
RegVal* make_ireg(const TypeSpec& ts, RegClass reg_class) override;
void constrain_reg(IRegConstraint constraint) override;
RegVal* lexical_lookup(goos::Object sym) override;
BlockEnv* find_block(const std::string& name) override;
~GlobalEnv() = default;
FileEnv* add_file(std::string name);
// TODO - consider refactoring to use a Trie
std::vector<std::string> list_files_with_prefix(const std::string& prefix);
std::vector<std::unique_ptr<FileEnv>>& get_files();
private:
std::vector<std::unique_ptr<FileEnv>> m_files;
};
/*!
* An Env for an entire file (or input to the REPL)
*/
class FileEnv : public Env {
public:
FileEnv(Env* parent, std::string name);
std::string print() override;
void add_function(std::unique_ptr<FunctionEnv> fe);
void add_top_level_function(std::unique_ptr<FunctionEnv> fe);
void add_static(std::unique_ptr<StaticObject> s);
void debug_print_tl();
const std::vector<std::unique_ptr<FunctionEnv>>& functions() { return m_functions; }
const std::vector<std::unique_ptr<StaticObject>>& statics() { return m_statics; }
std::string get_anon_function_name() {
return "anon-function-" + std::to_string(m_anon_func_counter++);
}
const FunctionEnv& top_level_function() {
ASSERT(m_top_level_func);
return *m_top_level_func;
}
const std::string& name() { return m_name; }
bool is_empty();
~FileEnv() = default;
template <typename T, class... Args>
T* alloc_val(Args&&... args) {
std::unique_ptr<T> new_obj = std::make_unique<T>(std::forward<Args>(args)...);
m_vals.push_back(std::move(new_obj));
return (T*)m_vals.back().get();
}
int default_segment() const { return m_default_segment; }
void set_nondebug_file() { m_default_segment = MAIN_SEGMENT; }
void set_debug_file() { m_default_segment = DEBUG_SEGMENT; }
bool is_debug_file() const { return default_segment() == DEBUG_SEGMENT; }
void cleanup_after_codegen();
// TODO - privatize these eventually
std::unordered_set<std::string> m_required_files; // TODO - a string for now
std::unordered_set<std::string> m_missing_required_files; // TODO - a string for now
protected:
std::string m_name;
std::vector<std::unique_ptr<FunctionEnv>> m_functions;
std::vector<std::unique_ptr<StaticObject>> m_statics;
int m_anon_func_counter = 0;
std::vector<std::unique_ptr<Val>> m_vals;
int m_default_segment = MAIN_SEGMENT;
// statics
FunctionEnv* m_top_level_func = nullptr;
};
/*!
* An Env which manages the scope for (declare ...) statements.
*/
class DeclareEnv : public Env {
public:
explicit DeclareEnv(EnvKind kind, Env* parent) : Env(kind, parent) {}
virtual std::string print() = 0;
~DeclareEnv() = default;
struct Settings {
bool is_set = false; // has the user set these with a (declare)?
bool inline_by_default = false; // if a function, inline when possible?
bool save_code = true; // if a function, should we save the code?
bool allow_inline = false; // should we allow the user to use this an inline function
bool print_asm = false; // should we print out the asm for this function?
} settings;
};
class IR_GotoLabel;
struct UnresolvedGoto {
IR_GotoLabel* ir = nullptr;
std::string label;
};
class IR_ConditionalBranch;
struct UnresolvedConditionalGoto {
IR_ConditionalBranch* ir = nullptr;
std::string label;
};
class FunctionEnv : public DeclareEnv {
public:
FunctionEnv(Env* parent, std::string name, const goos::Reader* reader);
std::string print() override;
std::unordered_map<std::string, Label>& get_label_map() override;
void set_segment(int seg) { segment = seg; }
void emit(const goos::Object& form, std::unique_ptr<IR> ir, Env* lowest_env);
void finish();
RegVal* make_ireg(const TypeSpec& ts, RegClass reg_class) override;
const std::vector<std::unique_ptr<IR>>& code() const { return m_code; }
const std::vector<goos::Object>& code_source() const { return m_code_debug_source; }
int max_vars() const { return m_iregs.size(); }
const std::vector<IRegConstraint>& constraints() { return m_constraints; }
void constrain(const IRegConstraint& c) { m_constraints.push_back(c); }
void set_allocations(AllocationResult&& result) { m_regalloc_result = std::move(result); }
RegVal* lexical_lookup(goos::Object sym) override;
const AllocationResult& alloc_result() { return m_regalloc_result; }
bool needs_aligned_stack() const { return m_aligned_stack_required; }
void require_aligned_stack() { m_aligned_stack_required = true; }
Label* alloc_unnamed_label() {
m_unnamed_labels.emplace_back(std::make_unique<Label>());
return m_unnamed_labels.back().get();
}
const std::string& name() const { return m_name; }
struct StackSpace {
int start_slot;
int slot_count;
};
StackSpace allocate_aligned_stack_space(int size_bytes, int align_bytes);
StackVarAddrVal* allocate_aligned_stack_variable(const TypeSpec& ts,
int size_bytes,
int align_bytes);
StackVarAddrVal* allocate_stack_singleton(const TypeSpec& ts, int size_bytes, int align_bytes);
int stack_slots_used_for_stack_vars() const { return m_stack_var_slots_used; }
int segment_for_static_data() {
if (segment == TOP_LEVEL_SEGMENT) {
return file_env()->default_segment();
} else {
return segment;
}
}
int idx_in_file = -1;
template <typename T, class... Args>
T* alloc_val(Args&&... args) {
std::unique_ptr<T> new_obj = std::make_unique<T>(std::forward<Args>(args)...);
m_vals.push_back(std::move(new_obj));
return (T*)m_vals.back().get();
}
template <typename T, class... Args>
T* alloc_env(Args&&... args) {
std::unique_ptr<T> new_obj = std::make_unique<T>(std::forward<Args>(args)...);
m_envs.push_back(std::move(new_obj));
return (T*)m_envs.back().get();
}
const std::vector<std::unique_ptr<RegVal>>& reg_vals() const { return m_iregs; }
RegVal* push_reg_val(std::unique_ptr<RegVal> in);
int segment = -1;
std::string method_of_type_name = "#f";
TypeSpec method_function_type;
std::optional<int> method_id;
bool is_asm_func = false;
bool asm_func_saved_regs = false;
TypeSpec asm_func_return_type;
std::vector<UnresolvedGoto> unresolved_gotos;
std::vector<UnresolvedConditionalGoto> unresolved_cond_gotos;
std::unordered_map<goos::InternedSymbolPtr, RegVal*, goos::InternedSymbolPtr::hash> params;
protected:
void resolve_gotos();
std::string m_name;
std::vector<std::unique_ptr<IR>> m_code;
std::vector<goos::Object> m_code_debug_source;
std::vector<std::unique_ptr<RegVal>> m_iregs;
std::vector<std::unique_ptr<Val>> m_vals;
std::vector<std::unique_ptr<Env>> m_envs;
std::vector<IRegConstraint> m_constraints;
AllocationResult m_regalloc_result;
bool m_aligned_stack_required = false;
int m_stack_var_slots_used = 0;
std::unordered_map<std::string, Label> m_labels;
std::vector<std::unique_ptr<Label>> m_unnamed_labels;
std::unordered_map<std::string, StackSpace> m_stack_singleton_slots;
const goos::Reader* m_reader = nullptr;
};
class BlockEnv : public Env {
public:
BlockEnv(Env* parent, std::string name);
std::string print() override;
BlockEnv* find_block(const std::string& name) override;
std::string name;
Label end_label = nullptr;
RegVal* return_value = nullptr;
std::vector<TypeSpec> return_types;
};
class LexicalEnv : public DeclareEnv {
public:
explicit LexicalEnv(Env* parent) : DeclareEnv(EnvKind::OTHER_ENV, parent) {}
RegVal* lexical_lookup(goos::Object sym) override;
std::string print() override;
std::unordered_map<goos::InternedSymbolPtr, RegVal*, goos::InternedSymbolPtr::hash> vars;
};
class LabelEnv : public Env {
public:
explicit LabelEnv(Env* parent) : Env(EnvKind::OTHER_ENV, parent) {}
std::string print() override { return "labelenv"; }
std::unordered_map<std::string, Label>& get_label_map() override;
BlockEnv* find_block(const std::string& name) override;
protected:
std::unordered_map<std::string, Label> m_labels;
};
class SymbolMacroEnv : public Env {
public:
explicit SymbolMacroEnv(Env* parent) : Env(EnvKind::SYMBOL_MACRO_ENV, parent) {}
// key is goos symbols.
std::unordered_map<goos::InternedSymbolPtr, goos::Object, goos::InternedSymbolPtr::hash> macros;
std::string print() override { return "symbol-macro-env"; }
};
class MacroExpandEnv : public Env {
public:
MacroExpandEnv(Env* parent,
const goos::InternedSymbolPtr macro_name,
const goos::Object& macro_body,
const goos::Object& macro_use)
: Env(EnvKind::MACRO_EXPAND_ENV, parent),
m_macro_name(macro_name),
m_macro_body(macro_body),
m_macro_use_location(macro_use) {
MacroExpandEnv* parent_macro = nullptr;
if (parent) {
parent_macro = parent->macro_expand_env();
}
if (parent_macro) {
m_root_form = parent_macro->m_root_form;
} else {
m_root_form = m_macro_use_location;
}
}
std::string print() override { return "macro-env"; }
const goos::InternedSymbolPtr name() const { return m_macro_name; }
const goos::Object& macro_body() const { return m_macro_body; }
const goos::Object& macro_use_location() const { return m_macro_use_location; }
const goos::Object& root_form() const { return m_root_form; }
private:
const goos::InternedSymbolPtr m_macro_name = {nullptr};
goos::Object m_macro_body;
goos::Object m_macro_use_location;
goos::Object m_root_form;
};
template <typename T>
T* get_parent_env_of_type_slow(Env* in) {
for (;;) {
auto attempt = dynamic_cast<T*>(in);
if (attempt)
return attempt;
if (dynamic_cast<GlobalEnv*>(in)) {
return nullptr;
}
in = in->parent();
}
}
inline Env::Env(EnvKind kind, Env* parent) : m_kind(kind), m_parent(parent) {
if (m_kind == EnvKind::FILE_ENV) {
m_lowest_envs.file_env = static_cast<FileEnv*>(this);
} else {
m_lowest_envs.file_env = m_parent ? m_parent->m_lowest_envs.file_env : nullptr;
}
if (m_kind == EnvKind::FUNCTION_ENV) {
m_lowest_envs.function_env = static_cast<FunctionEnv*>(this);
} else {
m_lowest_envs.function_env = m_parent ? m_parent->m_lowest_envs.function_env : nullptr;
}
if (m_kind == EnvKind::SYMBOL_MACRO_ENV) {
m_lowest_envs.symbol_macro_env = static_cast<SymbolMacroEnv*>(this);
} else {
m_lowest_envs.symbol_macro_env = m_parent ? m_parent->m_lowest_envs.symbol_macro_env : nullptr;
}
if (m_kind == EnvKind::MACRO_EXPAND_ENV) {
m_lowest_envs.macro_expand_env = static_cast<MacroExpandEnv*>(this);
} else {
m_lowest_envs.macro_expand_env = m_parent ? m_parent->m_lowest_envs.macro_expand_env : nullptr;
}
}