Add support for stack integers (#135)

* add support for stack integers

* update documentation

* revise value type stack variables
This commit is contained in:
water111
2020-11-22 12:22:19 -05:00
committed by GitHub
parent 66c9f40e13
commit 460ec874bb
19 changed files with 288 additions and 41 deletions
+3 -1
View File
@@ -44,4 +44,6 @@
- Creating a field of 128-bit value type no longer causes a compiler crash
- 128-bit fields are inspected as `<cannot-print>`
- Static fields can now contain floating point values
- Fixed a bug where loading a float from an object and immediately using it math would cause a compiler crash
- Fixed a bug where loading a float from an object and immediately using it math would cause a compiler crash
- Arrays of value types can be created on the stack with `new`.
+36 -1
View File
@@ -605,7 +605,10 @@ Get element from pair
The type of the result is always `object`, as pairs can hold any `object`. The type-check for `car` and `cdr` is relaxed - it allows it to be applied to any `pair` or `object`. The reason for allowing `object` is so you can write `(car (car x))` instead of `(car (the pair (car x)))`. However, if the argument to `car` is not a `pair`, you will get garbage or a crash.
## `new`
See section on creating new GOAL objects
```lisp
(new [allocation] [new-type-specification] [args])
```
See section on creating new GOAL objects.
## `print-type`
Print the type of some GOAL expression at compile time.
@@ -863,6 +866,38 @@ These can differ by padding for alignment.
## Built-in Methods
## New - How To Create GOAL Objects
GOAL has several different ways to create objects, all using the `new` form.
### Heap Allocated Objects
A new object can be allocated on a heap with `(new 'global 'obj-type [new-method-arguments])`.
This simply calls the `new` method of the given type. You can also replace `'global` with `'debug` to allocate on the debug heap.
Currently these are the only two heaps supported, in the future you will be able to call the new method with other arguments
to allow you to do an "in place new" or allocate on a different heap.
This will only work on structures and basics. If you want a heap allocated float/integer/pointer, create an array of size 1.
This will work on dynamically sized items.
### Heap Allocated Arrays
You can construct a heap array with `(new 'global 'inline-array 'obj-type count)` or `(new 'global 'array 'obj-type count)`.
These objects are not initialized. Note that the `array` version creates a `(pointer obj-type)` plain array,
__not__ a GOAL `array` type fancy array. In the future this may change because it is confusing.
Because these objects are uninitialized, you cannot provide constructor arguments.
You cannot use this on dynamically sized member types. However, the array size can be determined at runtime.
### Static Objects
You can create a static object with `(new 'static 'obj-type [field-def]...)`. For now it must be a structure or basic.
Each field def looks like `:field-name field-value`. The `field-value` is evaluated at compile time. For now, fields
can only be integers, floats, or symbols.
Fields which aren't explicitly initialized are zeroed, except for the type field of basics, which is properly initialized to the correct type.
This does not work on dynamically sized structures.
### Stack Allocated Objects
Currently only arrays of integers, floats, or pointers can be stack allocated.
For example, use `(new 'array 'int32 1)` to get a `(pointer int32)`. Unlike heap allocated arrays, these stack arrays
must have a size that can be determined at compile time.
## Defining a `new` Method
+3 -2
View File
@@ -83,8 +83,9 @@ void CodeGenerator::do_function(FunctionEnv* env, int f_idx) {
// do we include an extra push to get 8 more bytes to keep the stack aligned?
bool bonus_push = false;
// the offset to add directly to rsp for stack variables (no push/pop)
int manually_added_stack_offset = GPR_SIZE * allocs.stack_slots;
// the offset to add directly to rsp for stack variables or spills (no push/pop)
int manually_added_stack_offset =
GPR_SIZE * (allocs.stack_slots_for_spills + allocs.stack_slots_for_vars);
stack_offset += manually_added_stack_offset;
// do we need to align or manually offset?
+1
View File
@@ -165,6 +165,7 @@ void Compiler::color_object_file(FileEnv* env) {
input.max_vars = f->max_vars();
input.constraints = f->constraints();
input.stack_slots_for_stack_vars = f->stack_slots_used_for_stack_vars();
if (m_settings.debug_print_regalloc) {
input.debug_settings.print_input = true;
+8
View File
@@ -261,6 +261,14 @@ RegVal* FunctionEnv::lexical_lookup(goos::Object sym) {
return kv->second;
}
StackVarAddrVal* FunctionEnv::allocate_stack_variable(const TypeSpec& ts, int size_bytes) {
require_aligned_stack();
int slots_used = (size_bytes + emitter::GPR_SIZE - 1) / emitter::GPR_SIZE;
auto result = alloc_val<StackVarAddrVal>(ts, m_stack_var_slots_used, slots_used);
m_stack_var_slots_used += slots_used;
return result;
}
///////////////////
// LexicalEnv
///////////////////
+4 -5
View File
@@ -157,19 +157,18 @@ class FunctionEnv : public DeclareEnv {
void constrain(const IRegConstraint& c) { m_constraints.push_back(c); }
void set_allocations(const AllocationResult& result) { m_regalloc_result = 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; }
StackVarAddrVal* allocate_stack_variable(const TypeSpec& ts, int size_bytes);
int stack_slots_used_for_stack_vars() const { return m_stack_var_slots_used; }
int idx_in_file = -1;
template <typename T, class... Args>
@@ -205,7 +204,7 @@ class FunctionEnv : public DeclareEnv {
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;
};
+55 -20
View File
@@ -11,6 +11,30 @@ Register get_reg(const RegVal* rv, const AllocationResult& allocs, emitter::IR_R
assert(ass.kind == Assignment::Kind::REGISTER);
return ass.reg;
}
void load_constant(u64 value,
emitter::ObjectGenerator* gen,
emitter::IR_Record irec,
Register dest_reg) {
s64 svalue = value;
if (svalue == 0) {
gen->add_instr(IGen::xor_gpr64_gpr64(dest_reg, dest_reg), irec);
} else if (svalue > 0) {
if (svalue < UINT32_MAX) {
gen->add_instr(IGen::mov_gpr64_u32(dest_reg, value), irec);
} else {
// need a real 64 bit load
gen->add_instr(IGen::mov_gpr64_u64(dest_reg, value), irec);
}
} else {
if (svalue >= INT32_MIN) {
gen->add_instr(IGen::mov_gpr64_s32(dest_reg, svalue), irec);
} else {
// need a real 64 bit load
gen->add_instr(IGen::mov_gpr64_u64(dest_reg, value), irec);
}
}
}
} // namespace
///////////
@@ -77,26 +101,7 @@ void IR_LoadConstant64::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
auto dest_reg = get_reg(m_dest, allocs, irec);
s64 svalue = m_value;
if (svalue == 0) {
gen->add_instr(IGen::xor_gpr64_gpr64(dest_reg, dest_reg), irec);
} else if (svalue > 0) {
if (svalue < UINT32_MAX) {
gen->add_instr(IGen::mov_gpr64_u32(dest_reg, m_value), irec);
} else {
// need a real 64 bit load
gen->add_instr(IGen::mov_gpr64_u64(dest_reg, m_value), irec);
}
} else {
if (svalue >= INT32_MIN) {
gen->add_instr(IGen::mov_gpr64_s32(dest_reg, svalue), irec);
} else {
// need a real 64 bit load
gen->add_instr(IGen::mov_gpr64_u64(dest_reg, m_value), irec);
}
}
load_constant(m_value, gen, irec, dest_reg);
}
/////////////////////
@@ -858,4 +863,34 @@ void IR_IntToFloat::do_codegen(emitter::ObjectGenerator* gen,
emitter::IR_Record irec) {
gen->add_instr(IGen::int32_to_float(get_reg(m_dest, allocs, irec), get_reg(m_src, allocs, irec)),
irec);
}
///////////////////////
// GetStackAddr
///////////////////////
IR_GetStackAddr::IR_GetStackAddr(const RegVal* dest, int slot) : m_dest(dest), m_slot(slot) {}
std::string IR_GetStackAddr::print() {
return fmt::format("mov {}, stack-slot-{}", m_dest->print(), m_slot);
}
RegAllocInstr IR_GetStackAddr::to_rai() {
RegAllocInstr rai;
rai.write.push_back(m_dest->ireg());
return rai;
}
void IR_GetStackAddr::do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) {
auto dest_reg = get_reg(m_dest, allocs, irec);
int offset = GPR_SIZE * (m_slot + allocs.stack_slots_for_spills);
// dest = offset
load_constant(offset, gen, irec, dest_reg);
// dest = offset + RSP
gen->add_instr(IGen::add_gpr64_gpr64(dest_reg, RSP), irec);
// dest = offset + RSP - offset
gen->add_instr(IGen::sub_gpr64_gpr64(dest_reg, gRegInfo.get_offset_reg()), irec);
}
+14
View File
@@ -353,4 +353,18 @@ class IR_IntToFloat : public IR {
const RegVal* m_src = nullptr;
};
class IR_GetStackAddr : public IR {
public:
IR_GetStackAddr(const RegVal* dest, int slot);
std::string print() override;
RegAllocInstr to_rai() override;
void do_codegen(emitter::ObjectGenerator* gen,
const AllocationResult& allocs,
emitter::IR_Record irec) override;
private:
const RegVal* m_dest = nullptr;
int m_slot = -1;
};
#endif // JAK_IR_H
+12 -2
View File
@@ -27,7 +27,9 @@ RegVal* Val::to_xmm(Env* fe) {
return rv;
} else {
assert(false);
throw std::runtime_error("Register is not an XMM[0-15] register.");
auto re = fe->make_xmm(coerce_to_reg_type(m_ts));
fe->emit(std::make_unique<IR_RegSet>(re, rv));
return re;
}
}
@@ -115,6 +117,7 @@ RegVal* MemoryOffsetVal::to_reg(Env* fe) {
}
RegVal* MemoryDerefVal::to_reg(Env* fe) {
// todo, support better loads/stores from the stack
auto base_as_co = dynamic_cast<MemoryOffsetConstantVal*>(base);
if (base_as_co) {
auto re = fe->make_gpr(coerce_to_reg_type(m_ts));
@@ -130,6 +133,7 @@ RegVal* MemoryDerefVal::to_reg(Env* fe) {
}
RegVal* MemoryDerefVal::to_xmm(Env* fe) {
// todo, support better loads/stores from the stack
auto base_as_co = dynamic_cast<MemoryOffsetConstantVal*>(base);
if (base_as_co) {
auto re = fe->make_xmm(coerce_to_reg_type(m_ts));
@@ -168,4 +172,10 @@ RegVal* PairEntryVal::to_reg(Env* fe) {
info.size = 4;
fe->emit(std::make_unique<IR_LoadConstOffset>(re, offset, base->to_gpr(fe), info));
return re;
}
}
RegVal* StackVarAddrVal::to_reg(Env* fe) {
auto re = fe->make_gpr(coerce_to_reg_type(m_ts));
fe->emit(std::make_unique<IR_GetStackAddr>(re, m_slot));
return re;
}
+18 -5
View File
@@ -152,6 +152,23 @@ struct MemLoadInfo {
int size = -1;
};
/*!
* A spot on the stack.
*/
class StackVarAddrVal : public Val {
public:
StackVarAddrVal(TypeSpec ts, int slot, int slot_count)
: Val(std::move(ts)), m_slot(slot), m_slot_count(slot_count) {}
int slot() const { return m_slot; }
int slot_count() const { return m_slot_count; }
std::string print() const override { return "stack-" + std::to_string(m_slot); }
RegVal* to_reg(Env* fe) override;
private:
int m_slot, m_slot_count;
};
class MemoryOffsetConstantVal : public Val {
public:
MemoryOffsetConstantVal(TypeSpec ts, Val* _base, int _offset)
@@ -174,9 +191,6 @@ class MemoryOffsetVal : public Val {
Val* offset = nullptr;
};
// MemOffConstant
// MemOffVar
class MemoryDerefVal : public Val {
public:
MemoryDerefVal(TypeSpec ts, Val* _base, MemLoadInfo _info)
@@ -225,8 +239,7 @@ class FloatConstantVal : public Val {
protected:
StaticFloat* m_value = nullptr;
};
// IntegerConstant
// FloatConstant
// Bitfield
#endif // JAK_VAL_H
+43 -1
View File
@@ -507,7 +507,7 @@ Val* Compiler::compile_addr_of(const goos::Object& form, const goos::Object& res
auto loc = compile_error_guard(args.unnamed.at(0), env);
auto as_mem_deref = dynamic_cast<MemoryDerefVal*>(loc);
if (!as_mem_deref) {
throw_compile_error(form, "Cannot take the address of this");
throw_compile_error(form, "Cannot take the address of this " + loc->print());
}
return as_mem_deref->base;
}
@@ -574,6 +574,8 @@ Val* Compiler::compile_print_type(const goos::Object& form, const goos::Object&
Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest, Env* env) {
// todo - support compound types.
// todo - stack arrays?
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto allocation = quoted_sym_as_string(pair_car(_rest));
auto rest = &pair_cdr(_rest);
@@ -649,6 +651,46 @@ Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest,
if (is_structure(type_of_object)) {
return compile_new_static_structure_or_basic(form, type_of_object, *rest, env);
}
} else if (allocation == "stack") {
auto type_of_object = m_ts.make_typespec(type_as_string);
printf("type as string is %s\n", type_as_string.c_str());
if (type_as_string == "inline-array" || type_as_string == "array") {
bool is_inline = type_as_string == "inline-array";
auto elt_type = quoted_sym_as_string(pair_car(*rest));
rest = &pair_cdr(*rest);
auto count_obj = pair_car(*rest);
rest = &pair_cdr(*rest);
// try to get the size as a compile time constant.
int64_t constant_count = 0;
bool is_constant_size = try_getting_constant_integer(count_obj, &constant_count, env);
if (!is_constant_size) {
throw_compile_error(form, "cannot create a dynamically sized stack array");
}
if (!rest->is_empty_list()) {
// got extra arguments
throw_compile_error(form, "new array form got more arguments than expected");
}
auto ts = is_inline ? m_ts.make_inline_array_typespec(elt_type)
: m_ts.make_pointer_typespec(elt_type);
auto info = m_ts.get_deref_info(ts);
if (!info.can_deref) {
throw_compile_error(form,
fmt::format("Cannot make an {} of {}\n", type_as_string, ts.print()));
}
if (!m_ts.lookup_type(elt_type)->is_reference()) {
// not a reference type
int size_in_bytes = info.stride * constant_count;
auto addr = fe->allocate_stack_variable(ts, size_in_bytes);
return addr;
}
// todo, stack structures.
}
// todo, stack not-arrays
}
throw_compile_error(form, "unsupported new form");
+2 -1
View File
@@ -160,7 +160,8 @@ AllocationResult allocate_registers(const AllocationInput& input) {
// prepare the result
result.ok = true;
result.needs_aligned_stack_for_spills = cache.used_stack;
result.stack_slots = cache.current_stack_slot;
result.stack_slots_for_spills = cache.current_stack_slot;
result.stack_slots_for_vars = input.stack_slots_for_stack_vars;
// copy over the assignment result
result.assignment.resize(cache.max_var);
+4 -2
View File
@@ -68,8 +68,9 @@ struct AllocationResult {
std::vector<std::vector<Assignment>> assignment; // variable, instruction
std::vector<LiveInfo> ass_as_ranges; // another format, maybe easier?
std::vector<emitter::Register> used_saved_regs; // which saved regs get clobbered?
int stack_slots = 0; // how many space on the stack do we need?
std::vector<StackOp> stack_ops; // additional instructions to spill/restore
int stack_slots_for_spills = 0; // how many space on the stack do we need?
int stack_slots_for_vars = 0;
std::vector<StackOp> stack_ops; // additional instructions to spill/restore
bool needs_aligned_stack_for_spills = false;
};
@@ -81,6 +82,7 @@ struct AllocationInput {
std::vector<IRegConstraint> constraints; // all register constraints
int max_vars = -1; // maximum register id.
std::vector<std::string> debug_instruction_names; // optional, for debug prints
int stack_slots_for_stack_vars = 0;
struct {
bool print_input = false;
@@ -0,0 +1,7 @@
(let* ((x (new 'stack 'array 'int8 1))
(x-addr (the uint x)))
(if (and (> x-addr #x7ffff00)
(< x-addr #x7ffffff))
1
0)
)
@@ -0,0 +1,9 @@
(let ((x (new 'stack 'array 'int32 1))
(y (new 'stack 'array 'int8 2))
(z (new 'stack 'array 'int8 1)))
(set! (-> x) 10)
(set! (-> z) 0)
(set! (-> y) #xfffffffff)
(set! (-> y 1) 3)
(+ (-> x) (-> y) (-> z) (-> y 1))
)
@@ -0,0 +1,59 @@
;; like test-approx-pi, but using stack variables
(start-test "approx-pi-stack")
(defun test-approx-pi-stack ((res integer))
(let ((rad (new 'stack 'array 'int32 1))
(count (new 'stack 'array 'uint32 1)))
(set! (-> rad) (* res res))
(set! (-> count) 0)
(dotimes (x res)
(dotimes (y res)
(if (> (-> rad) (+ (* x x) (* y y)))
(+! (-> count) 1)
)
)
)
(* 4.0 (/ (the float (-> count)) (the float (-> rad))))
)
)
(let ((approx-pi (test-approx-pi-stack 1000)))
(expect-true (> approx-pi 3.14))
(expect-true (< approx-pi 3.15))
)
(defun test-approx-pi-float-stack ((res float))
(let* ((rad (new 'stack 'array 'float 1))
(count (new 'stack 'array 'float 1))
(x (new 'stack 'array 'float 1))
(y (new 'stack 'array 'float 1))
(scale (new 'stack 'array 'float 1)))
(set! (-> rad) (* res res))
(set! (-> count) 0.0)
(set! (-> x) 0.0)
(set! (-> y) (-> x))
(set! (-> scale) (/ 1.0 (-> rad)))
(while (< (-> x) res)
(set! (-> y) 0.0)
(while (< (-> y) res)
(if (> (-> rad) (+ (* (-> x) (-> x)) (* (-> y) (-> y))))
(+! (-> count) (-> scale))
)
(+! (-> y) 1.0)
)
(+! (-> x) 1.0)
)
(* 4.0 (-> count))
)
)
(let ((approx-pi (test-approx-pi-float-stack 500.0)))
(expect-true (> approx-pi 3.14))
(expect-true (< approx-pi 3.15))
)
(finish-test)
@@ -29,7 +29,6 @@
(while (< x res)
(set! y 0.0)
(while (< y res)
; (format #t "tapf ~f ~f ~f~%" x y res)
(if (> rad (+ (* x x) (* y y)))
(+! count scale)
)
+5
View File
@@ -68,3 +68,8 @@ TEST_F(VariableTests, Let) {
runner.run_static_test(env, testCategory, "let-star.static.gc", {"30\n"});
runner.run_static_test(env, testCategory, "mlet.static.gc", {"10\n"});
}
TEST_F(VariableTests, StackVars) {
runner.run_static_test(env, testCategory, "stack-ints.gc", {"12\n"});
runner.run_static_test(env, testCategory, "stack-ints-2.gc", {"1\n"});
}
+5
View File
@@ -219,6 +219,11 @@ TEST_F(WithGameTests, ApproxPi) {
get_test_pass_string("approx-pi", 4));
}
TEST_F(WithGameTests, ApproxPiStack) {
runner.run_static_test(env, testCategory, "test-approx-pi-stack.gc",
get_test_pass_string("approx-pi-stack", 4));
}
TEST_F(WithGameTests, DynamicType) {
runner.run_static_test(env, testCategory, "test-dynamic-type.gc",
get_test_pass_string("dynamic-type", 4));