From 21fbdce7aac04729f618819d3e117c3fd667ebc2 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sun, 29 Nov 2020 18:01:30 -0500 Subject: [PATCH] [Compiler] Bitfield Types (#146) * add the ability to define and read bitfield types * new set * add bitfield setting * add static bitfields --- common/type_system/Type.cpp | 49 ++++++ common/type_system/Type.h | 44 +++-- common/type_system/TypeSystem.cpp | 61 +++++++ common/type_system/TypeSystem.h | 16 ++ common/type_system/deftype.cpp | 139 ++++++++++++++- doc/changelog.md | 8 +- goalc/compiler/Compiler.h | 11 ++ goalc/compiler/IR.cpp | 21 ++- goalc/compiler/IR.h | 4 +- goalc/compiler/Util.cpp | 4 + goalc/compiler/Val.cpp | 40 ++++- goalc/compiler/Val.h | 31 +++- goalc/compiler/compilation/Atoms.cpp | 6 +- goalc/compiler/compilation/Define.cpp | 162 ++++++++++-------- goalc/compiler/compilation/Function.cpp | 3 + goalc/compiler/compilation/Math.cpp | 51 ++++++ goalc/compiler/compilation/Static.cpp | 83 +++++++++ goalc/compiler/compilation/Type.cpp | 22 ++- goalc/emitter/IGen.h | 15 +- .../arithmetic/shift-fixed.static.gc | 2 + .../with_game/test-bitfield-access.gc | 21 +++ .../with_game/test-bitfield-tricky-access.gc | 66 +++++++ .../with_game/test-set-bitfield.gc | 22 +++ .../with_game/test-static-bitfield.gc | 13 ++ test/goalc/test_arithmetic.cpp | 7 +- test/goalc/test_with_game.cpp | 18 ++ 26 files changed, 819 insertions(+), 100 deletions(-) create mode 100644 test/goalc/source_templates/arithmetic/shift-fixed.static.gc create mode 100644 test/goalc/source_templates/with_game/test-bitfield-access.gc create mode 100644 test/goalc/source_templates/with_game/test-bitfield-tricky-access.gc create mode 100644 test/goalc/source_templates/with_game/test-set-bitfield.gc create mode 100644 test/goalc/source_templates/with_game/test-static-bitfield.gc diff --git a/common/type_system/Type.cpp b/common/type_system/Type.cpp index 755f63ee01..541fc93da9 100644 --- a/common/type_system/Type.cpp +++ b/common/type_system/Type.cpp @@ -502,6 +502,15 @@ bool StructureType::operator==(const Type& other) const { // clang-format on } +bool BitFieldType::operator==(const Type& other) const { + if (typeid(*this) != typeid(other)) { + return false; + } + + auto* p_other = dynamic_cast(&other); + return other.is_equal(*this) && m_fields == p_other->m_fields; +} + int StructureType::get_size_in_memory() const { return m_size_in_mem; } @@ -568,4 +577,44 @@ std::string BasicType::print() const { int BasicType::get_offset() const { return BASIC_OFFSET; +} + +///////////////// +// Bitfield +///////////////// + +BitField::BitField(TypeSpec type, std::string name, int offset, int size) + : m_type(std::move(type)), m_name(std::move(name)), m_offset(offset), m_size(size) {} + +bool BitField::operator==(const BitField& other) const { + return m_type == other.m_type && m_name == other.m_name && m_offset == other.m_offset && + other.m_size == m_size; +} + +BitFieldType::BitFieldType(std::string parent, std::string name, int size, bool sign_extend) + : ValueType(std::move(parent), std::move(name), false, size, sign_extend, RegKind::GPR_64) {} + +bool BitFieldType::lookup_field(const std::string& name, BitField* out) const { + for (auto& field : m_fields) { + if (field.name() == name) { + *out = field; + return true; + } + } + return false; +} + +std::string BitField::print() const { + return fmt::format("[{} {}] sz {} off {}", name(), type().print(), size(), offset()); +} + +std::string BitFieldType::print() const { + std::string result; + result += fmt::format("Parent type: {}\nFields:\n", get_parent()); + for (auto& field : m_fields) { + result += fmt::format(" {}\n", field.print()); + } + result += fmt::format("Mem size: {}, load size: {}, signed {}, align {}\n", get_size_in_memory(), + get_load_size(), get_load_signed(), get_in_memory_alignment()); + return result; } \ No newline at end of file diff --git a/common/type_system/Type.h b/common/type_system/Type.h index 76af676556..081af6fb5b 100644 --- a/common/type_system/Type.h +++ b/common/type_system/Type.h @@ -174,12 +174,12 @@ class Field { void set_inline(); std::string print() const; const TypeSpec& type() const { return m_type; } - bool is_inline() const { return m_inline; } - bool is_array() const { return m_array; } - bool is_dynamic() const { return m_dynamic; } + const std::string& name() const { return m_name; } + int offset() const { return m_offset; } + bool operator==(const Field& other) const; int alignment() const { assert(m_alignment != -1); @@ -191,17 +191,11 @@ class Field { return m_array_size; } - const std::string& name() const { return m_name; } - - int offset() const { return m_offset; } - - bool operator==(const Field& other) const; - private: friend class TypeSystem; void set_alignment(int alignment) { m_alignment = alignment; } - void set_offset(int offset) { m_offset = offset; } + std::string m_name; TypeSpec m_type; int m_offset = -1; @@ -260,8 +254,34 @@ class BasicType : public StructureType { ~BasicType() = default; }; -class BitField {}; +class BitField { + public: + BitField() = default; + BitField(TypeSpec type, std::string name, int offset, int size); + const std::string name() const { return m_name; } + int offset() const { return m_offset; } + int size() const { return m_size; } + const TypeSpec& type() const { return m_type; } + bool operator==(const BitField& other) const; + std::string print() const; -class BitFieldType : ValueType {}; + private: + TypeSpec m_type; + std::string m_name; + int m_offset = -1; // in bits + int m_size = -1; // in bits. +}; + +class BitFieldType : public ValueType { + public: + BitFieldType(std::string parent, std::string name, int size, bool sign_extend); + bool lookup_field(const std::string& name, BitField* out) const; + std::string print() const override; + bool operator==(const Type& other) const override; + + private: + friend class TypeSystem; + std::vector m_fields; +}; #endif // JAK_TYPE_H diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 6d8a31991b..e5c1ad5830 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -1307,4 +1307,65 @@ ReverseDerefInfo TypeSystem::get_reverse_deref_info(const ReverseDerefInputInfo& ReverseDerefInfo result; result.success = reverse_deref(input, &result.deref_path, &result.addr_of, &result.result_type); return result; +} + +/*! + * Is the given type a bitfield type? + */ +bool TypeSystem::is_bitfield_type(const std::string& type_name) const { + return dynamic_cast(lookup_type(type_name)); +} + +/*! + * Get information about a field within a bitfield type. + */ +BitfieldLookupInfo TypeSystem::lookup_bitfield_info(const std::string& type_name, + const std::string& field_name) const { + auto type = get_type_of_type(type_name); + BitField f; + if (!type->lookup_field(field_name, &f)) { + fmt::print("[TypeSystem] Type {} has no bitfield named {}\n", type_name, field_name); + throw std::runtime_error("lookup_bitfield failed"); + } + + BitfieldLookupInfo result; + result.result_type = f.type(); + result.offset = f.offset(); + result.sign_extend = lookup_type(result.result_type)->get_load_signed(); + result.size = f.size(); + return result; +} + +/*! + * Add a new field to a bitfield type. + * Set the field size to -1 if you want to just use the size of the type and not clip it. + */ +void TypeSystem::add_field_to_bitfield(BitFieldType* type, + const std::string& field_name, + const TypeSpec& field_type, + int offset, + int field_size) { + // in bits + auto load_size = lookup_type(field_type)->get_load_size() * 8; + if (field_size == -1) { + field_size = load_size; + } + + if (field_size > load_size) { + fmt::print( + "[TypeSystem] Type {}'s bitfield {}'s set size is {}, which is larger than the actual " + "type: {}\n", + type->get_name(), field_name, field_size, load_size); + throw std::runtime_error("Failed to add bitfield to type"); + } + + if (field_size + offset > type->get_load_size() * 8) { + fmt::print( + "[TypeSystem] Type {}'s bitfield {} will run off the end of the type (ends at {} bits, " + "type is {} bits)\n", + type->get_name(), field_name, field_size + offset, type->get_load_size() * 8); + throw std::runtime_error("Failed to add bitfield to type"); + } + BitField field(field_type, field_name, offset, field_size); + type->m_fields.push_back(field); } \ No newline at end of file diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index d32c6de977..fd0f1adbfb 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -24,6 +24,13 @@ struct FieldLookupInfo { int array_size = -1; }; +struct BitfieldLookupInfo { + TypeSpec result_type; + int offset = -1; + int size = -1; + bool sign_extend = false; +}; + struct DerefInfo { bool can_deref = false; bool mem_deref = false; @@ -102,6 +109,8 @@ class TypeSystem { FieldLookupInfo lookup_field_info(const std::string& type_name, const std::string& field_name) const; + BitfieldLookupInfo lookup_bitfield_info(const std::string& type_name, + const std::string& field_name) const; void assert_field_offset(const std::string& type_name, const std::string& field_name, int offset); int add_field_to_type(StructureType* type, const std::string& field_name, @@ -122,6 +131,13 @@ class TypeSystem { std::vector get_path_up_tree(const std::string& type); int get_next_method_id(Type* type); + bool is_bitfield_type(const std::string& type_name) const; + void add_field_to_bitfield(BitFieldType* type, + const std::string& field_name, + const TypeSpec& field_type, + int offset, + int field_size); + /*! * Get a type by name and cast to a child class of Type*. Must succeed. */ diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp index d8965d3796..023f890d7c 100644 --- a/common/type_system/deftype.cpp +++ b/common/type_system/deftype.cpp @@ -136,7 +136,44 @@ void add_field(StructureType* structure, TypeSystem* ts, const goos::Object& def } } -void declare_method(StructureType* type, TypeSystem* type_system, const goos::Object& def) { +void add_bitfield(BitFieldType* bitfield_type, TypeSystem* ts, const goos::Object& def) { + auto rest = &def; + + auto name = symbol_string(car(rest)); + rest = cdr(rest); + + auto type = parse_typespec(ts, car(rest)); + rest = cdr(rest); + + int offset_override = -1; + int size_override = -1; + + if (!rest->is_empty_list()) { + while (!rest->is_empty_list()) { + auto opt_name = symbol_string(car(rest)); + rest = cdr(rest); + + if (opt_name == ":offset") { + offset_override = get_int(car(rest)); + rest = cdr(rest); + } else if (opt_name == ":size") { + size_override = get_int(car(rest)); + rest = cdr(rest); + } else { + throw std::runtime_error("Invalid option in field specification: " + opt_name); + } + } + } + + if (offset_override == -1) { + throw std::runtime_error("Bitfield type must manually specify offsets always"); + } + + // it's fine if the size is -1, that means it'll just use the type's size. + ts->add_field_to_bitfield(bitfield_type, name, type, offset_override, size_override); +} + +void declare_method(Type* type, TypeSystem* type_system, const goos::Object& def) { for_each_in_list(def, [&](const goos::Object& _obj) { auto obj = &_obj; // (name args return-type [id]) @@ -281,6 +318,97 @@ StructureDefResult parse_structure_def(StructureType* type, return result; } +struct BitFieldTypeDefResult { + TypeFlags flags; + bool generate_runtime_type = true; +}; + +BitFieldTypeDefResult parse_bitfield_type_def(BitFieldType* type, + TypeSystem* ts, + const goos::Object& fields, + const goos::Object& options) { + BitFieldTypeDefResult result; + for_each_in_list(fields, [&](const goos::Object& o) { add_bitfield(type, ts, o); }); + TypeFlags flags; + flags.heap_base = 0; + flags.size = type->get_size_in_memory(); + flags.pad = 0; + + auto* rest = &options; + int size_assert = -1; + int method_count_assert = -1; + uint64_t flag_assert = 0; + bool flag_assert_set = false; + while (!rest->is_empty_list()) { + if (car(rest).is_pair()) { + auto opt_list = &car(rest); + auto& first = car(opt_list); + opt_list = cdr(opt_list); + + if (symbol_string(first) == ":methods") { + declare_method(type, ts, *opt_list); + } else { + throw std::runtime_error("Invalid option list in field specification: " + + car(rest).print()); + } + + rest = cdr(rest); + } else { + auto opt_name = symbol_string(car(rest)); + rest = cdr(rest); + + if (opt_name == ":size-assert") { + size_assert = get_int(car(rest)); + if (size_assert == -1) { + throw std::runtime_error("Cannot use -1 as size-assert"); + } + rest = cdr(rest); + } else if (opt_name == ":method-count-assert") { + method_count_assert = get_int(car(rest)); + if (method_count_assert == -1) { + throw std::runtime_error("Cannot use -1 as method_count_assert"); + } + rest = cdr(rest); + } else if (opt_name == ":flag-assert") { + flag_assert = get_int(car(rest)); + flag_assert_set = true; + rest = cdr(rest); + } else if (opt_name == ":no-runtime-type") { + result.generate_runtime_type = false; + } else if (opt_name == ":heap-base") { + u16 hb = get_int(car(rest)); + rest = cdr(rest); + flags.heap_base = hb; + } else { + throw std::runtime_error("Invalid option in field specification: " + opt_name); + } + } + } + + if (size_assert != -1 && flags.size != u16(size_assert)) { + throw std::runtime_error("Type " + type->get_name() + " came out to size " + + std::to_string(int(flags.size)) + " but size-assert was set to " + + std::to_string(size_assert)); + } + + flags.methods = ts->get_next_method_id(type); + + if (method_count_assert != -1 && flags.methods != u16(method_count_assert)) { + throw std::runtime_error( + "Type " + type->get_name() + " has " + std::to_string(int(flags.methods)) + + " methods, but method-count-assert was set to " + std::to_string(method_count_assert)); + } + + if (flag_assert_set && (flags.flag != flag_assert)) { + throw std::runtime_error( + fmt::format("Type {} has flag 0x{:x} but flag-assert was set to 0x{:x}", type->get_name(), + flags.flag, flag_assert)); + } + + result.flags = flags; + return result; +} + } // namespace TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src) { @@ -350,7 +478,14 @@ DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) { } ts->add_type(name, std::move(new_type)); } else if (is_type("integer", parent_type, ts)) { - throw std::runtime_error("Creating a child type of integer is not supported yet."); + auto pto = ts->lookup_type(parent_type); + assert(pto); + auto new_type = std::make_unique( + parent_type_name, name, pto->get_size_in_memory(), pto->get_load_signed()); + auto sr = parse_bitfield_type_def(new_type.get(), ts, field_list_obj, options_obj); + result.flags = sr.flags; + result.create_runtime_type = sr.generate_runtime_type; + ts->add_type(name, std::move(new_type)); } else { throw std::runtime_error("Creating a child type from " + parent_type.print() + " is not allowed or not supported yet."); diff --git a/doc/changelog.md b/doc/changelog.md index 53378171c2..e68ac41bb0 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -51,4 +51,10 @@ ## V0.2 - Breaking change: return type of a function using `return-from #f` to return a value from the entire function is now the lowest common ancestor of all possible return values. - Fixed bug where `return-from` could reach outside of an inlined function. -- Fixed bug where `return-from` might not behave correctly when returning from inside a let inside an inlined function. \ No newline at end of file +- Fixed bug where `return-from` might not behave correctly when returning from inside a let inside an inlined function. +- Added `fmin` and `fmax` floating point min and max. These work on multiple arguments and use the `minss`/`maxss` instructions for the best performance. +- Added `imul64` instruction for doing a real 64-bit multiplication. This must be used when porting code that looks at the `hi` register after an EE `mult`. +- Added `shl`, `shr`, and `sar` shifts which take a constant integer. These cannot be used with a variable shift amount. +- Added bitfield types to the type system +- Added the ability to cast integers to bitfield types +- Fixed a bug where casting between integer types with `the` that did not involve emitting code would permanently change the type of the variable. \ No newline at end of file diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 41c83a90d8..9ebd96cfb4 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -49,6 +49,8 @@ class Compiler { void init_logger(); void init_settings(); bool try_getting_macro_from_goos(const goos::Object& macro_name, goos::Object* dest); + void set_bitfield(const goos::Object& form, BitFieldVal* dst, RegVal* src, Env* env); + Val* do_set(const goos::Object& form, Val* dst, RegVal* src, Env* env); Val* compile_goos_macro(const goos::Object& o, const goos::Object& macro_obj, const goos::Object& rest, @@ -89,6 +91,7 @@ class Compiler { bool is_quoted_sym(const goos::Object& o); bool is_basic(const TypeSpec& ts); bool is_structure(const TypeSpec& ts); + bool is_bitfield(const TypeSpec& ts); const goos::Object& pair_car(const goos::Object& o); const goos::Object& pair_cdr(const goos::Object& o); void expect_empty_list(const goos::Object& o); @@ -134,6 +137,7 @@ class Compiler { bool is_none(Val* in); Val* compile_variable_shift(const RegVal* in, const RegVal* sa, Env* env, IntegerMathKind kind); + Val* compile_fixed_shift(const RegVal* in, u8 sa, Env* env, IntegerMathKind kind); Val* compile_format_string(const goos::Object& form, Env* env, @@ -160,6 +164,10 @@ class Compiler { const TypeSpec& type, const goos::Object& field_defs, Env* env); + Val* compile_new_static_bitfield(const goos::Object& form, + const TypeSpec& type, + const goos::Object& field_defs, + Env* env); public: // Atoms @@ -225,6 +233,9 @@ class Compiler { Val* compile_shlv(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_sarv(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_shrv(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_shl(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_sar(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_shr(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_mod(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_logxor(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_lognot(const goos::Object& form, const goos::Object& rest, Env* env); diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index 99e99ac484..b1ff333d50 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -392,6 +392,9 @@ void IR_FunctionAddr::do_codegen(emitter::ObjectGenerator* gen, IR_IntegerMath::IR_IntegerMath(IntegerMathKind kind, RegVal* dest, RegVal* arg) : m_kind(kind), m_dest(dest), m_arg(arg) {} +IR_IntegerMath::IR_IntegerMath(IntegerMathKind kind, RegVal* dest, u8 shift_amount) + : m_kind(kind), m_dest(dest), m_shift_amount(shift_amount) {} + std::string IR_IntegerMath::print() { switch (m_kind) { case IntegerMathKind::ADD_64: @@ -412,6 +415,12 @@ std::string IR_IntegerMath::print() { return fmt::format("shlv {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::SHRV_64: return fmt::format("shrv {}, {}", m_dest->print(), m_arg->print()); + case IntegerMathKind::SAR_64: + return fmt::format("sar {}, {}", m_dest->print(), m_shift_amount); + case IntegerMathKind::SHL_64: + return fmt::format("shl {}, {}", m_dest->print(), m_shift_amount); + case IntegerMathKind::SHR_64: + return fmt::format("shr {}, {}", m_dest->print(), m_shift_amount); case IntegerMathKind::AND_64: return fmt::format("and {}, {}", m_dest->print(), m_arg->print()); case IntegerMathKind::OR_64: @@ -430,7 +439,8 @@ RegAllocInstr IR_IntegerMath::to_rai() { rai.write.push_back(m_dest->ireg()); rai.read.push_back(m_dest->ireg()); - if (m_kind != IntegerMathKind::NOT_64) { + if (m_kind != IntegerMathKind::NOT_64 && m_kind != IntegerMathKind::SHL_64 && + m_kind != IntegerMathKind::SAR_64 && m_kind != IntegerMathKind::SHR_64) { rai.read.push_back(m_arg->ireg()); } @@ -480,6 +490,15 @@ void IR_IntegerMath::do_codegen(emitter::ObjectGenerator* gen, gen->add_instr(IGen::sar_gpr64_cl(get_reg(m_dest, allocs, irec)), irec); assert(get_reg(m_arg, allocs, irec) == emitter::RCX); break; + case IntegerMathKind::SHL_64: + gen->add_instr(IGen::shl_gpr64_u8(get_reg(m_dest, allocs, irec), m_shift_amount), irec); + break; + case IntegerMathKind::SHR_64: + gen->add_instr(IGen::shr_gpr64_u8(get_reg(m_dest, allocs, irec), m_shift_amount), irec); + break; + case IntegerMathKind::SAR_64: + gen->add_instr(IGen::sar_gpr64_u8(get_reg(m_dest, allocs, irec), m_shift_amount), irec); + break; case IntegerMathKind::IMUL_32: { auto dr = get_reg(m_dest, allocs, irec); gen->add_instr(IGen::imul_gpr32_gpr32(dr, get_reg(m_arg, allocs, irec)), irec); diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index a79804c2d5..045746e29b 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -211,6 +211,7 @@ enum class IntegerMathKind { class IR_IntegerMath : public IR { public: IR_IntegerMath(IntegerMathKind kind, RegVal* dest, RegVal* arg); + IR_IntegerMath(IntegerMathKind kind, RegVal* dest, u8 shift_amount); std::string print() override; RegAllocInstr to_rai() override; void do_codegen(emitter::ObjectGenerator* gen, @@ -221,7 +222,8 @@ class IR_IntegerMath : public IR { protected: IntegerMathKind m_kind; RegVal* m_dest; - RegVal* m_arg; + RegVal* m_arg = nullptr; + u8 m_shift_amount = 0; }; enum class FloatMathKind { DIV_SS, MUL_SS, ADD_SS, SUB_SS, MIN_SS, MAX_SS }; diff --git a/goalc/compiler/Util.cpp b/goalc/compiler/Util.cpp index 60aba59b7e..cf5866659f 100644 --- a/goalc/compiler/Util.cpp +++ b/goalc/compiler/Util.cpp @@ -150,6 +150,10 @@ bool Compiler::is_structure(const TypeSpec& ts) { return m_ts.typecheck(m_ts.make_typespec("structure"), ts, "", false, false); } +bool Compiler::is_bitfield(const TypeSpec& ts) { + return m_ts.is_bitfield_type(ts.base_type()); +} + bool Compiler::try_getting_constant_integer(const goos::Object& in, int64_t* out, Env* env) { (void)env; if (in.is_int()) { diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index 17c64cde81..ccd4105875 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -26,7 +26,6 @@ RegVal* Val::to_xmm(Env* fe) { if (rv->ireg().kind == emitter::RegKind::XMM) { return rv; } else { - assert(false); auto re = fe->make_xmm(coerce_to_reg_type(m_ts)); fe->emit(std::make_unique(re, rv)); return re; @@ -179,3 +178,42 @@ RegVal* StackVarAddrVal::to_reg(Env* fe) { fe->emit(std::make_unique(re, m_slot)); return re; } + +std::string BitFieldVal::print() const { + return fmt::format("[bitfield sz {} off {} sx {} of {}]", m_size, m_offset, m_sign_extend, + m_parent->print()); +} + +RegVal* BitFieldVal::to_reg(Env* env) { + // first get the parent value + auto parent_reg = m_parent->to_gpr(env); + + auto fe = get_parent_env_of_type(env); + auto result = fe->make_ireg(coerce_to_reg_type(m_ts), emitter::RegKind::GPR); + env->emit(std::make_unique(result, parent_reg)); + + int start_bit = m_offset; + int end_bit = m_offset + m_size; + int epad = 64 - end_bit; + assert(epad >= 0); + int spad = start_bit; + + // shift left as much as possible to kill upper bits + if (epad > 0) { + env->emit(std::make_unique(IntegerMathKind::SHL_64, result, epad)); + } + + int next_shift = epad + spad; + assert(next_shift + m_size == 64); + assert(next_shift >= 0); + + if (next_shift > 0) { + if (m_sign_extend) { + env->emit(std::make_unique(IntegerMathKind::SAR_64, result, next_shift)); + } else { + env->emit(std::make_unique(IntegerMathKind::SHR_64, result, next_shift)); + } + } + + return result; +} \ No newline at end of file diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index 3e4c76eee6..e66d07aeb7 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -85,6 +85,7 @@ class RegVal : public Val { class SymbolVal : public Val { public: SymbolVal(std::string name, TypeSpec ts) : Val(std::move(ts)), m_name(std::move(name)) { + // this is for define, which looks at the SymbolVal and not the SymbolValueVal. mark_as_settable(); } const std::string& name() const { return m_name; } @@ -98,10 +99,14 @@ class SymbolVal : public Val { class SymbolValueVal : public Val { public: SymbolValueVal(const SymbolVal* sym, TypeSpec ts, bool sext) - : Val(std::move(ts)), m_sym(sym), m_sext(sext) {} + : Val(std::move(ts)), m_sym(sym), m_sext(sext) { + // this is for set, which looks at the Symbol's Value. + mark_as_settable(); + } const std::string& name() const { return m_sym->name(); } std::string print() const override { return "[<" + name() + ">]"; } RegVal* to_reg(Env* fe) override; + const SymbolVal* sym() const { return m_sym; } protected: const SymbolVal* m_sym = nullptr; @@ -240,6 +245,28 @@ class FloatConstantVal : public Val { StaticFloat* m_value = nullptr; }; -// Bitfield +class BitFieldVal : public Val { + public: + BitFieldVal(TypeSpec ts, Val* parent, int offset, int size, bool sign_extend) + : Val(std::move(ts)), + m_parent(parent), + m_offset(offset), + m_size(size), + m_sign_extend(sign_extend) { + m_is_settable = parent->settable(); + } + std::string print() const override; + RegVal* to_reg(Env* env) override; + int offset() const { return m_offset; } + int size() const { return m_size; } + bool sext() const { return m_sign_extend; } + Val* parent() { return m_parent; } + + protected: + Val* m_parent = nullptr; + int m_offset = -1; + int m_size = -1; + bool m_sign_extend = false; +}; #endif // JAK_VAL_H diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 446105fbdb..39116eed86 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -106,9 +106,9 @@ static const std::unordered_map< {"shlv", &Compiler::compile_shlv}, {"shrv", &Compiler::compile_shrv}, {"sarv", &Compiler::compile_sarv}, - // {"shl", &Compiler::compile_shl}, - // {"shr", &Compiler::compile_shr}, - // {"sar", &Compiler::compile_sar}, + {"shl", &Compiler::compile_shl}, + {"shr", &Compiler::compile_shr}, + {"sar", &Compiler::compile_sar}, {"mod", &Compiler::compile_mod}, {"logior", &Compiler::compile_logior}, {"logxor", &Compiler::compile_logxor}, diff --git a/goalc/compiler/compilation/Define.cpp b/goalc/compiler/compilation/Define.cpp index 1d04f5f41a..a225cdd065 100644 --- a/goalc/compiler/compilation/Define.cpp +++ b/goalc/compiler/compilation/Define.cpp @@ -88,6 +88,99 @@ Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Objec return get_none(); } +void Compiler::set_bitfield(const goos::Object& form, BitFieldVal* dst, RegVal* src, Env* env) { + assert(!dst->sext()); + auto fe = get_parent_env_of_type(env); + + // first, get the value we want to modify: + auto original_original = dst->parent()->to_gpr(env); + // let's not directly modify original, and instead create a copy then use do_set on parent. + // this way we avoid "cheating" the set system, although it should be safe... + auto original = fe->make_gpr(original_original->type()); + env->emit(std::make_unique(original, original_original)); + + // we'll need a temp register to hold a mask: + auto temp = fe->make_gpr(src->type()); + // mask value should be 1's everywhere except for the field so we can AND with it + u64 mask_val = ~(((1 << dst->size()) - 1) << dst->offset()); + env->emit(std::make_unique(temp, mask_val)); + // modify the original! + env->emit(std::make_unique(IntegerMathKind::AND_64, original, temp)); + + // put the source in temp + env->emit(std::make_unique(temp, src)); + + // to shift us all the way to the left and clear upper bits + int left_shift_amnt = 64 - dst->size(); + int right_shift_amnt = (64 - dst->size()) - dst->offset(); + assert(right_shift_amnt >= 0); + + if (left_shift_amnt > 0) { + env->emit(std::make_unique(IntegerMathKind::SHL_64, temp, left_shift_amnt)); + } + + if (right_shift_amnt > 0) { + env->emit(std::make_unique(IntegerMathKind::SHR_64, temp, right_shift_amnt)); + } + + env->emit(std::make_unique(IntegerMathKind::OR_64, original, temp)); + do_set(form, dst->parent(), original, env); +} + +/*! + * The internal "set" logic. + */ +Val* Compiler::do_set(const goos::Object& form, Val* dest, RegVal* source, Env* env) { + if (!dest->settable()) { + throw_compile_error(form, + "Tried to use set! on something that wasn't settable: " + dest->print()); + } + auto as_mem_deref = dynamic_cast(dest); + auto as_pair = dynamic_cast(dest); + auto as_reg = dynamic_cast(dest); + auto as_sym_val = dynamic_cast(dest); + auto as_bitfield = dynamic_cast(dest); + + if (as_mem_deref) { + // setting somewhere in memory + auto base = as_mem_deref->base; + auto base_as_mco = dynamic_cast(base); + if (base_as_mco) { + // if it is a constant offset, we can use a fancy x86-64 addressing mode to simplify + auto ti = m_ts.lookup_type(as_mem_deref->type()); + env->emit(std::make_unique( + source, base_as_mco->offset, base_as_mco->base->to_gpr(env), ti->get_load_size())); + return source; + } else { + // nope, the pointer to dereference is some compliated thing. + auto ti = m_ts.lookup_type(as_mem_deref->type()); + env->emit( + std::make_unique(source, 0, base->to_gpr(env), ti->get_load_size())); + return source; + } + } else if (as_pair) { + // this could probably be part of MemoryDerefVal and not a special case here. + env->emit(std::make_unique(source, as_pair->is_car ? -2 : 2, + as_pair->base->to_gpr(env), 4)); + return source; + } else if (as_reg) { + typecheck(form, as_reg->type(), source->type(), "set! lexical variable"); + env->emit(std::make_unique(as_reg, source)); + return source; + } else if (as_sym_val) { + typecheck(form, as_sym_val->type(), source->type(), "set! global symbol"); + auto result_in_gpr = source->to_gpr(env); + env->emit(std::make_unique(as_sym_val->sym(), result_in_gpr)); + return result_in_gpr; + } else if (as_bitfield) { + set_bitfield(form, as_bitfield, source, env); + return get_none(); + } + + throw_compile_error(form, "Set not implemented for: " + dest->print()); + return get_none(); +} + /*! * Set something to something. * Lots of special cases. @@ -101,71 +194,6 @@ Val* Compiler::compile_set(const goos::Object& form, const goos::Object& rest, E // and to_reg'd first, then the destination is computed, if the destination requires math to // compute. auto source = compile_error_guard(args.unnamed.at(1), env)->to_reg(env); - - if (destination.is_symbol()) { - // destination is just a symbol, so it's either a lexical variable or a global. - - // first, attempt a lexical set: - auto lex_place = env->lexical_lookup(destination); - if (lex_place) { - // typecheck and set! - typecheck(form, lex_place->type(), source->type(), "set! lexical variable"); - env->emit(std::make_unique(lex_place, source)); - return source; - } else { - // try to set symbol - auto existing = m_symbol_types.find(destination.as_symbol()->name); - if (existing == m_symbol_types.end()) { - throw_compile_error( - form, "could not find something called " + symbol_string(destination) + " to set!"); - } else { - typecheck(form, existing->second, source->type(), "set! global symbol"); - auto fe = get_parent_env_of_type(env); - auto sym_val = - fe->alloc_val(symbol_string(destination), m_ts.make_typespec("symbol")); - auto result_in_gpr = source->to_gpr(env); - if (!sym_val->settable()) { - throw_compile_error( - form, "Tried to use set! on something that wasn't settable: " + sym_val->print()); - } - env->emit(std::make_unique(sym_val, result_in_gpr)); - return result_in_gpr; - } - } - } else { - // destination is some complex expression, so compile it and hopefully get something settable. - auto dest = compile_error_guard(destination, env); - if (!dest->settable()) { - throw_compile_error(form, - "Tried to use set! on something that wasn't settable: " + dest->print()); - } - auto as_mem_deref = dynamic_cast(dest); - auto as_pair = dynamic_cast(dest); - if (as_mem_deref) { - // setting somewhere in memory - auto base = as_mem_deref->base; - auto base_as_mco = dynamic_cast(base); - if (base_as_mco) { - // if it is a constant offset, we can use a fancy x86-64 addressing mode to simplify - auto ti = m_ts.lookup_type(as_mem_deref->type()); - env->emit(std::make_unique( - source, base_as_mco->offset, base_as_mco->base->to_gpr(env), ti->get_load_size())); - return source; - } else { - // nope, the pointer to dereference is some compliated thing. - auto ti = m_ts.lookup_type(as_mem_deref->type()); - env->emit(std::make_unique(source, 0, base->to_gpr(env), - ti->get_load_size())); - return source; - } - } else if (as_pair) { - // this could probably be part of MemoryDerefVal and not a special case here. - env->emit(std::make_unique(source, as_pair->is_car ? -2 : 2, - as_pair->base->to_gpr(env), 4)); - return source; - } else { - throw_compile_error(form, "Set not implemented for this yet"); - } - } - throw std::runtime_error("Unexpected error in Set"); + auto dest = compile_error_guard(destination, env); + return do_set(form, dest, source, env); } \ No newline at end of file diff --git a/goalc/compiler/compilation/Function.cpp b/goalc/compiler/compilation/Function.cpp index 1807f865d4..a2eb5095ea 100644 --- a/goalc/compiler/compilation/Function.cpp +++ b/goalc/compiler/compilation/Function.cpp @@ -150,6 +150,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest IRegConstraint constr; constr.instr_idx = 0; // constraint at function start auto ireg = new_func_env->make_ireg(lambda.params.at(i).type, emitter::RegKind::GPR); + ireg->mark_as_settable(); constr.ireg = ireg->ireg(); constr.desired_register = emitter::gRegInfo.get_arg_reg(i); new_func_env->params[lambda.params.at(i).name] = ireg; @@ -350,6 +351,7 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en auto type = eval_args.at(i)->type(); auto copy = env->make_ireg(type, get_preferred_reg_kind(type)); env->emit(std::make_unique(copy, eval_args.at(i))); + copy->mark_as_settable(); lexical_env->vars[head_as_lambda->lambda.params.at(i).name] = copy; } @@ -494,6 +496,7 @@ Val* Compiler::compile_real_function_call(const goos::Object& form, std::vector arg_outs; for (auto& arg : args) { arg_outs.push_back(env->make_ireg(arg->type(), emitter::RegKind::GPR)); + arg_outs.back()->mark_as_settable(); env->emit(std::make_unique(arg_outs.back(), arg)); } diff --git a/goalc/compiler/compilation/Math.cpp b/goalc/compiler/compilation/Math.cpp index fb05d60d49..749965406f 100644 --- a/goalc/compiler/compilation/Math.cpp +++ b/goalc/compiler/compilation/Math.cpp @@ -442,6 +442,57 @@ Val* Compiler::compile_variable_shift(const RegVal* in, return result; } +Val* Compiler::compile_shl(const goos::Object& form, const goos::Object& rest, Env* env) { + auto args = get_va(form, rest); + va_check(form, args, {{}, {goos::ObjectType::INTEGER}}, {}); + auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env); + auto sa = args.unnamed.at(1).as_int(); + if (sa < 0 || sa > 64) { + throw_compile_error(form, "Cannot shift by more than 64, or by a negative amount"); + } + return compile_fixed_shift(first, sa, env, IntegerMathKind::SHL_64); +} + +Val* Compiler::compile_shr(const goos::Object& form, const goos::Object& rest, Env* env) { + auto args = get_va(form, rest); + va_check(form, args, {{}, {goos::ObjectType::INTEGER}}, {}); + auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env); + auto sa = args.unnamed.at(1).as_int(); + if (sa < 0 || sa > 64) { + throw_compile_error(form, "Cannot shift by more than 64, or by a negative amount"); + } + return compile_fixed_shift(first, sa, env, IntegerMathKind::SHR_64); +} + +Val* Compiler::compile_sar(const goos::Object& form, const goos::Object& rest, Env* env) { + auto args = get_va(form, rest); + va_check(form, args, {{}, {goos::ObjectType::INTEGER}}, {}); + auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env); + auto sa = args.unnamed.at(1).as_int(); + if (sa < 0 || sa > 64) { + throw_compile_error(form, "Cannot shift by more than 64, or by a negative amount"); + } + return compile_fixed_shift(first, sa, env, IntegerMathKind::SAR_64); +} + +Val* Compiler::compile_fixed_shift(const RegVal* in, u8 sa, Env* env, IntegerMathKind kind) { + // type check + if (get_math_mode(in->type()) != MathMode::MATH_INT) { + throw std::runtime_error("Can't shift a " + in->type().print()); + } + + if (sa > 64) { + throw std::runtime_error("Can't shift by more than 64"); + } + + // copy to result register + auto result = env->make_gpr(in->type()); + env->emit(std::make_unique(result, in)); + // do the shift + env->emit(std::make_unique(kind, result, sa)); + return result; +} + Val* Compiler::compile_mod(const goos::Object& form, const goos::Object& rest, Env* env) { auto args = get_va(form, rest); va_check(form, args, {{}, {}}, {}); diff --git a/goalc/compiler/compilation/Static.cpp b/goalc/compiler/compilation/Static.cpp index 12ec68a3a3..a7c40e4bcc 100644 --- a/goalc/compiler/compilation/Static.cpp +++ b/goalc/compiler/compilation/Static.cpp @@ -34,8 +34,91 @@ bool integer_fits(s64 in, int size, bool is_signed) { assert(false); } } + +u32 float_as_u32(float x) { + u32 result; + memcpy(&result, &x, 4); + return result; +} } // namespace +Val* Compiler::compile_new_static_bitfield(const goos::Object& form, + const TypeSpec& type, + const goos::Object& _field_defs, + Env* env) { + auto fe = get_parent_env_of_type(env); + u64 as_int = 0; + + auto type_info = dynamic_cast(m_ts.lookup_type(type)); + assert(type_info); + assert(type_info->get_load_size() <= 8); + + auto* field_defs = &_field_defs; + while (!field_defs->is_empty_list()) { + auto field_name_def = symbol_string(pair_car(*field_defs)); + field_defs = &pair_cdr(*field_defs); + + auto field_value = pair_car(*field_defs); + field_defs = &pair_cdr(*field_defs); + + if (field_name_def.at(0) != ':') { + throw_compile_error(form, + "expected field def name to start with :, instead got " + field_name_def); + } + + field_name_def = field_name_def.substr(1); + auto field_info = m_ts.lookup_bitfield_info(type_info->get_name(), field_name_def); + + auto field_offset = field_info.offset; + auto field_size = field_info.size; + assert(field_offset + field_size <= type_info->get_load_size() * 8); + + if (is_integer(field_info.result_type)) { + s64 value = 0; + if (!try_getting_constant_integer(field_value, &value, env)) { + throw_compile_error(form, + fmt::format("Field {} is an integer, but the value given couldn't be " + "converted to an integer at compile time.", + field_name_def)); + } + + // todo, check the integer fits! + u64 unsigned_value = value; + u64 or_value = unsigned_value; + // shift us all the way left to clear upper bits. + or_value <<= (64 - field_size); + // and back right. + or_value >>= (64 - field_size); + if (or_value != unsigned_value) { + throw_compile_error(form, fmt::format("Field {}'s value doesn't fit.", field_name_def)); + } + + as_int |= (or_value << field_offset); + } else if (is_float(field_info.result_type)) { + if (field_size != 32) { + throw_compile_error(form, + fmt::format("Tried to put a float into a float bitfield that's not 4 " + "bytes. This is probably not what you wanted to do.")); + } + + float value = 0.f; + if (!try_getting_constant_float(field_value, &value, env)) { + throw_compile_error(form, fmt::format("Field {} is a float, but the value given couldn't " + "be converted to a float at compile time.", + field_name_def)); + } + u64 float_value = float_as_u32(value); + as_int |= (float_value << field_offset); + } + + else { + assert(false); // for now + } + } + + return fe->alloc_val(type, as_int); +} + Val* Compiler::compile_new_static_structure_or_basic(const goos::Object& form, const TypeSpec& type, const goos::Object& _field_defs, diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index 4bd5855a44..3803e04aea 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -464,7 +464,13 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest continue; } - // todo try bitfield + auto bitfield_type = dynamic_cast(type_info); + if (bitfield_type) { + auto bitfield_info = m_ts.lookup_bitfield_info(type_info->get_name(), field_name); + result = fe->alloc_val(bitfield_info.result_type, result, bitfield_info.offset, + bitfield_info.size, bitfield_info.sign_extend); + continue; + } } auto index_value = compile_error_guard(field_obj, env)->to_gpr(env); @@ -546,8 +552,13 @@ Val* Compiler::compile_the(const goos::Object& form, const goos::Object& rest, E if (m_ts.typecheck(m_ts.make_typespec("integer"), desired_ts, "", false, false)) { auto result = number_to_integer(base, env); - result->set_type(desired_ts); - return result; + if (result != base) { + result->set_type(desired_ts); + return result; + } else { + result = get_parent_env_of_type(env)->alloc_val(desired_ts, base); + return result; + } } if (m_ts.typecheck(m_ts.make_typespec("float"), desired_ts, "", false, false)) { @@ -651,6 +662,11 @@ 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); } + + if (is_bitfield(type_of_object)) { + return compile_new_static_bitfield(form, type_of_object, *rest, env); + } + } else if (allocation == "stack") { auto type_of_object = m_ts.make_typespec(type_as_string); diff --git a/goalc/emitter/IGen.h b/goalc/emitter/IGen.h index 111f34bf4f..a2b1132ea1 100644 --- a/goalc/emitter/IGen.h +++ b/goalc/emitter/IGen.h @@ -1528,9 +1528,10 @@ class IGen { /*! * Shift 64-ptr left (logical) by the constant shift amount "sa". */ - static Instruction shl_gpr64_u8(uint8_t reg, uint8_t sa) { + static Instruction shl_gpr64_u8(Register reg, uint8_t sa) { + assert(reg.is_gpr()); Instruction instr(0xc1); - instr.set_modrm_and_rex(4, reg, 3, true); + instr.set_modrm_and_rex(4, reg.hw_id(), 3, true); instr.set(Imm(1, sa)); return instr; } @@ -1538,9 +1539,10 @@ class IGen { /*! * Shift 64-ptr right (logical) by the constant shift amount "sa". */ - static Instruction shr_gpr64_u8(uint8_t reg, uint8_t sa) { + static Instruction shr_gpr64_u8(Register reg, uint8_t sa) { + assert(reg.is_gpr()); Instruction instr(0xc1); - instr.set_modrm_and_rex(5, reg, 3, true); + instr.set_modrm_and_rex(5, reg.hw_id(), 3, true); instr.set(Imm(1, sa)); return instr; } @@ -1548,9 +1550,10 @@ class IGen { /*! * Shift 64-ptr right (arithmetic) by the constant shift amount "sa". */ - static Instruction sar_gpr64_u8(uint8_t reg, uint8_t sa) { + static Instruction sar_gpr64_u8(Register reg, uint8_t sa) { + assert(reg.is_gpr()); Instruction instr(0xc1); - instr.set_modrm_and_rex(7, reg, 3, true); + instr.set_modrm_and_rex(7, reg.hw_id(), 3, true); instr.set(Imm(1, sa)); return instr; } diff --git a/test/goalc/source_templates/arithmetic/shift-fixed.static.gc b/test/goalc/source_templates/arithmetic/shift-fixed.static.gc new file mode 100644 index 0000000000..3ea4f90d67 --- /dev/null +++ b/test/goalc/source_templates/arithmetic/shift-fixed.static.gc @@ -0,0 +1,2 @@ +(+ (shl 2 3) (shl 1 0) (shl 0 4) (shr 2 3) (shr 10 2) (shl -2 1) (sar -16 2)) +;; 16 1 0 0 2 -4 -4 \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-bitfield-access.gc b/test/goalc/source_templates/with_game/test-bitfield-access.gc new file mode 100644 index 0000000000..6c236f0421 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-bitfield-access.gc @@ -0,0 +1,21 @@ +(deftype test-bf-type (int32) + ((f1 int16 :offset 0) + (f2 uint8 :offset 16) + (f3 int8 :size 3 :offset 24) + (f4 uint8 :size 2 :offset 27) + (f5 int8 :size 2 :offset 29) + ) + ) + +(deftype test-bf-type2 (int64) + ((f1 int16 :offset 0) + (f2 uint8 :offset 16) + (f3 float :offset 24) + ) + ) + +(let ((temp (the test-bf-type #xf9f2f344)) + (temp2 (the test-bf-type2 #x133f456789012345))) + (format #t "~A" (< (fabs (- 1.7711 (+ 1.0 (-> temp2 f3)))) 0.002)) + (format #t "~X~X~X~X~X~%" (-> temp f1) (-> temp f2) (-> temp f3) (-> temp f4) (-> temp f5)) + ) \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-bitfield-tricky-access.gc b/test/goalc/source_templates/with_game/test-bitfield-tricky-access.gc new file mode 100644 index 0000000000..f8e99b430c --- /dev/null +++ b/test/goalc/source_templates/with_game/test-bitfield-tricky-access.gc @@ -0,0 +1,66 @@ +(start-test "bitfield-tricky-access") + +(deftype bitfield-test-type-4 (uint32) + ((a uint8 :offset 3) + (b uint16 :offset 19 :size 12) + ) + ) + +(deftype bitfield-in-structure-type (structure) + ((a uint32 :offset 0) + (bitfield bitfield-test-type-4 :offset 0) + ) + ) + +;; check we can use a bitfield in a symbol +(define *global-bitfield* (the bitfield-test-type-4 #x0)) +(set! (-> *global-bitfield* a) #xfe) +(set! (-> *global-bitfield* b) #x12341234) + +(expect-eq #xfe (-> *global-bitfield* a)) +(expect-eq #x234 (-> *global-bitfield* b)) +(expect-eq #x11A007F0 (the uint *global-bitfield*)) + + +;; check we can use a bitfield as a field inside a structure +(let ((heap-bitfield (new 'global 'bitfield-in-structure-type))) + (set! (-> heap-bitfield a) 0) + (set! (-> heap-bitfield bitfield a) #xfe) + (set! (-> heap-bitfield bitfield b) #x12341234) + (expect-eq #xfe (-> heap-bitfield bitfield a)) + (expect-eq #x234 (-> heap-bitfield bitfield b)) + (expect-eq #x11A007F0 (the uint (-> heap-bitfield bitfield))) + ) + + +(deftype bitfield-test-type-5 (uint32) + ((a uint8 :offset 0) + (b uint16 :offset 19 :size 12) + ) + ) + +;; check we can use a bitfield inside of a bitfield +(deftype nested-bitfield (uint64) + ((a uint8 :offset 2 :size 3) + (bitfield bitfield-test-type-5 :offset 5) + ) + ) + +(let ((thing (the nested-bitfield #x0))) + (set! (-> thing a) #xfffff) ;; shoudlbe truncated to #b111 + (expect-eq 7 (-> thing a)) + (expect-eq 0 (-> thing bitfield a)) + (set! (-> thing bitfield a ) #xffff) ;; truncated to #xff + (expect-eq #xff (-> thing bitfield a)) + (expect-eq 7 (-> thing a)) + ) + +(define *global-nested-bitfield* (the nested-bitfield 0)) + (set! (-> *global-nested-bitfield* a) #xfffff) ;; shoudlbe truncated to #b111 + (expect-eq 7 (-> *global-nested-bitfield* a)) + (expect-eq 0 (-> *global-nested-bitfield* bitfield a)) + (set! (-> *global-nested-bitfield* bitfield a ) #xffff) ;; truncated to #xff + (expect-eq #xff (-> *global-nested-bitfield* bitfield a)) + (expect-eq 7 (-> *global-nested-bitfield* a)) + +(finish-test) \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-set-bitfield.gc b/test/goalc/source_templates/with_game/test-set-bitfield.gc new file mode 100644 index 0000000000..a55c58afa1 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-set-bitfield.gc @@ -0,0 +1,22 @@ + + +(deftype test-bf-type3 (int64) + ((f1 uint16 :offset 0) + (f2 uint8 :size 7 :offset 16) + (f3 float :offset 23) + (f4 uint8 :size 1 :offset 55) + (f5 uint8 :offset 56) + ) + ) + +(let ((temp (the test-bf-type3 #x0))) + (set! (-> temp f1) #x12) + (set! (-> temp f2) #x13) + (set! (-> temp f3) 12.3433) + (set! (-> temp f4) #xffffffff) ; will get truncated. + (set! (-> temp f1) #x12) + (set! (-> temp f2) #x13) + (format #t "~A" (eq? 0 (-> temp f5))) ; check it gets truncated + (format #t "~f~%" (+ (-> temp f3) (-> temp f2) (-> temp f1) (-> temp f4))) + ) + diff --git a/test/goalc/source_templates/with_game/test-static-bitfield.gc b/test/goalc/source_templates/with_game/test-static-bitfield.gc new file mode 100644 index 0000000000..7ddaa837e0 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-static-bitfield.gc @@ -0,0 +1,13 @@ +(deftype test-bf-type7 (int64) + ((f1 uint16 :offset 0) + (f2 uint8 :size 7 :offset 16) + (f3 float :offset 23) + (f4 uint8 :size 1 :offset 55) + (f5 uint8 :offset 56) + ) + ) + +(let ((temp (new 'static 'test-bf-type7 :f1 #x12 :f2 #x13 :f3 12.3433 :f4 #x1))) + (format #t "~A" (eq? 0 (-> temp f5))) ; check it gets truncated + (format #t "~f~%" (+ (-> temp f3) (-> temp f2) (-> temp f1) (-> temp f4))) + ) \ No newline at end of file diff --git a/test/goalc/test_arithmetic.cpp b/test/goalc/test_arithmetic.cpp index 1e7f18f07d..09d37b252f 100644 --- a/test/goalc/test_arithmetic.cpp +++ b/test/goalc/test_arithmetic.cpp @@ -227,10 +227,15 @@ TEST_F(ArithmeticTests, NestedFunctionCall) { runner.run_static_test(env, testCategory, "nested-function.static.gc", {"10\n"}); } -TEST_F(ArithmeticTests, ShiftOperations) { +TEST_F(ArithmeticTests, VariableShift) { runner.run_static_test(env, testCategory, "shiftvs.static.gc", {"11\n"}); } +TEST_F(ArithmeticTests, FixedShift) { + // same math as the variable shift test, just using the fixed shift operators. + runner.run_static_test(env, testCategory, "shift-fixed.static.gc", {"11\n"}); +} + TEST_F(ArithmeticTests, Subtraction) { runner.run_static_test(env, testCategory, "subtract-1.static.gc", {"4\n"}); runner.run_static_test(env, testCategory, "subtract-2.static.gc", {"4\n"}); diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 9f4a980c4e..34dffd2aeb 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -302,6 +302,24 @@ TEST_F(WithGameTests, GameCount) { get_test_pass_string("game-count", 4)); } +TEST_F(WithGameTests, BitFieldAccess) { + runner.run_static_test(env, testCategory, "test-bitfield-access.gc", + {"#tfffffffffffff344f213ffffffffffffffff\n0\n"}); +} + +TEST_F(WithGameTests, SimpleBitField) { + runner.run_static_test(env, testCategory, "test-set-bitfield.gc", {"#t50.3432\n0\n"}); +} + +TEST_F(WithGameTests, StaticBitField) { + runner.run_static_test(env, testCategory, "test-static-bitfield.gc", {"#t50.3432\n0\n"}); +} + +TEST_F(WithGameTests, TrickyBitField) { + runner.run_static_test(env, testCategory, "test-bitfield-tricky-access.gc", + get_test_pass_string("bitfield-tricky-access", 14)); +} + TEST_F(WithGameTests, Math) { runner.run_static_test(env, testCategory, "test-math.gc", get_test_pass_string("math", 31)); }