From a6258f36547a0c7a4b2b1894c2489c8739467e62 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Tue, 18 May 2021 21:25:29 -0400 Subject: [PATCH] [compiler] support 128-bit bitfields (#500) * support setting and accessing fields of a 128-bit bitfield * remove print * rework static constants * support 128-bit bitfields as part of static structures * dynamic construction --- common/CMakeLists.txt | 1 + common/type_system/TypeSystem.cpp | 8 + common/type_system/defenum.cpp | 29 +-- common/type_system/deftype.cpp | 3 + common/util/BitUtils.cpp | 37 ++++ common/util/BitUtils.h | 7 +- docs/markdown/progress-notes/changelog.md | 9 +- goal_src/engine/level/level-h.gc | 1 + goalc/compiler/Compiler.h | 10 + goalc/compiler/ConstantValue.h | 87 ++++++++ goalc/compiler/StaticObject.cpp | 10 +- goalc/compiler/StaticObject.h | 31 +-- goalc/compiler/Val.cpp | 53 +++-- goalc/compiler/Val.h | 25 ++- goalc/compiler/compilation/Atoms.cpp | 7 +- goalc/compiler/compilation/Define.cpp | 93 +++++++-- goalc/compiler/compilation/Math.cpp | 8 +- goalc/compiler/compilation/Static.cpp | 189 ++++++++++-------- goalc/compiler/compilation/Type.cpp | 10 +- .../with_game/test-access-bitfield128.gc | 95 +++++++++ test/goalc/test_with_game.cpp | 13 ++ 21 files changed, 548 insertions(+), 178 deletions(-) create mode 100644 common/util/BitUtils.cpp create mode 100644 goalc/compiler/ConstantValue.h create mode 100644 test/goalc/source_templates/with_game/test-access-bitfield128.gc diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index a9404e2764..fde51701b3 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -16,6 +16,7 @@ add_library(common type_system/TypeFieldLookup.cpp type_system/TypeSpec.cpp type_system/TypeSystem.cpp + util/BitUtils.cpp util/dgo_util.cpp util/DgoReader.cpp util/DgoWriter.cpp diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index b2c5853ba3..44ffcdf8aa 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -1322,6 +1322,14 @@ void TypeSystem::add_field_to_bitfield(BitFieldType* type, "type is {} bits)\n", type->get_name(), field_name, field_size + offset, type->get_load_size() * 8); } + + // 128-bit bitfields have the limitation that fields cannot cross the 64-bit boundary. + if (offset < 64 && offset + field_size > 64) { + throw_typesystem_error( + "Type {}'s bitfield {} will cross bit 64, which is not permitted. Range [{}, {})", + type->get_name(), field_name, offset, offset + field_size); + } + BitField field(field_type, field_name, offset, field_size); type->m_fields.push_back(field); } diff --git a/common/type_system/defenum.cpp b/common/type_system/defenum.cpp index 40d6c80aab..268ada104d 100644 --- a/common/type_system/defenum.cpp +++ b/common/type_system/defenum.cpp @@ -8,6 +8,7 @@ #include "defenum.h" #include "deftype.h" #include "third-party/fmt/core.h" +#include "common/util/BitUtils.h" namespace { const goos::Object& car(const goos::Object* x) { @@ -30,34 +31,6 @@ bool is_type(const std::string& expected, const TypeSpec& actual, const TypeSyst return ts->tc(ts->make_typespec(expected), actual); } -bool integer_fits(s64 in, int size, bool is_signed) { - switch (size) { - case 1: - if (is_signed) { - return in >= INT8_MIN && in <= INT8_MAX; - } else { - return in >= 0 && in <= UINT8_MAX; - } - case 2: - if (is_signed) { - return in >= INT16_MIN && in <= INT16_MAX; - } else { - return in >= 0 && in <= UINT16_MAX; - } - case 4: - if (is_signed) { - return in >= INT32_MIN && in <= INT32_MAX; - } else { - return in >= 0 && in <= UINT32_MAX; - } - case 8: - return true; - default: - assert(false); - return false; - } -} - std::string symbol_string(const goos::Object& obj) { if (obj.is_symbol()) { return obj.as_symbol()->name; diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp index 4826ca2f31..21a04cd54d 100644 --- a/common/type_system/deftype.cpp +++ b/common/type_system/deftype.cpp @@ -475,6 +475,9 @@ DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) { assert(pto); auto new_type = std::make_unique( parent_type_name, name, pto->get_size_in_memory(), pto->get_load_signed()); + auto parent_value = dynamic_cast(pto); + assert(parent_value); + new_type->inherit(parent_value); new_type->set_runtime_type(pto->get_runtime_name()); auto sr = parse_bitfield_type_def(new_type.get(), ts, field_list_obj, options_obj); result.flags = sr.flags; diff --git a/common/util/BitUtils.cpp b/common/util/BitUtils.cpp new file mode 100644 index 0000000000..5e212180ec --- /dev/null +++ b/common/util/BitUtils.cpp @@ -0,0 +1,37 @@ +#include "BitUtils.h" + +#include + +bool integer_fits(s64 in, int size, bool is_signed) { + switch (size) { + case 1: + if (is_signed) { + return in >= INT8_MIN && in <= INT8_MAX; + } else { + return in >= 0 && in <= UINT8_MAX; + } + case 2: + if (is_signed) { + return in >= INT16_MIN && in <= INT16_MAX; + } else { + return in >= 0 && in <= UINT16_MAX; + } + case 4: + if (is_signed) { + return in >= INT32_MIN && in <= INT32_MAX; + } else { + return in >= 0 && in <= UINT32_MAX; + } + case 8: + return true; + default: + assert(false); + return false; + } +} + +u32 float_as_u32(float x) { + u32 result; + memcpy(&result, &x, 4); + return result; +} \ No newline at end of file diff --git a/common/util/BitUtils.h b/common/util/BitUtils.h index 0e0d1d12ff..d8d6d319e0 100644 --- a/common/util/BitUtils.h +++ b/common/util/BitUtils.h @@ -2,8 +2,8 @@ #include #include "common/util/assert.h" - #include "common/util/Range.h" +#include "common/common_types.h" constexpr int BITS_PER_BYTE = 8; template @@ -60,4 +60,7 @@ std::optional get_power_of_two(T in) { } else { return std::nullopt; } -} \ No newline at end of file +} + +bool integer_fits(s64 in, int size, bool is_signed); +u32 float_as_u32(float x); diff --git a/docs/markdown/progress-notes/changelog.md b/docs/markdown/progress-notes/changelog.md index 96cb6adc28..12957bff2e 100644 --- a/docs/markdown/progress-notes/changelog.md +++ b/docs/markdown/progress-notes/changelog.md @@ -148,4 +148,11 @@ - Invalid static pairs now have nice errors instead of exiting the compiler - Added unsigned division (previously signed division was used for unsigned numbers) - Use shifts (64-bit) for positive power of two multiply and divide. Otherwise use 32-bit. This matches GOAL. -- Allow setting a 64-bit or less memory location from a 128-bit variable (upper bits are discarded). \ No newline at end of file +- Allow setting a 64-bit or less memory location from a 128-bit variable (upper bits are discarded). +- It is now a compiler error to declare a bitfield type where a field crosses bit 64. +- Fixed a bug where a let/immediate lambda with an argument with type of child of int128/uint128 would end up in a 64 bit register. +- Support accessing and setting fields of a 128-bit bitfield type. +- Fixed a bug where the mask constant for clearing a bitfield was not computed correctly +- Support 128-bit bitfields inside of static structure +- Support 128-bit bitfield constants +- Support dynamic construction of 128-bit bitfield values diff --git a/goal_src/engine/level/level-h.gc b/goal_src/engine/level/level-h.gc index dbe91b958e..ebf6221901 100644 --- a/goal_src/engine/level/level-h.gc +++ b/goal_src/engine/level/level-h.gc @@ -40,6 +40,7 @@ (water-tex1 60) ;; merc1 61 ;; generic1 62 + ;; debug text 68 ) ;; Information related to visibility data for a level. diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 792782b1f2..bc661f0f71 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -64,6 +64,15 @@ class Compiler { goos::Object expand_macro_completely(const goos::Object& src, Env* env); void set_bitfield(const goos::Object& form, BitFieldVal* dst, RegVal* src, Env* env); + void set_bitfield_128(const goos::Object& form, BitFieldVal* dst, RegVal* src, Env* env); + + void set_bits_in_bitfield(int size, + int offset, + RegVal* dst, + RegVal* src, + FunctionEnv* fe, + Env* env); + Val* do_set(const goos::Object& form, Val* dst, RegVal* src_in_reg, Val* src, Env* env); Val* compile_goos_macro(const goos::Object& o, const goos::Object& macro_obj, @@ -71,6 +80,7 @@ class Compiler { Env* env); Val* compile_pair(const goos::Object& code, Env* env); Val* compile_integer(const goos::Object& code, Env* env); + Val* compile_integer(const U128& value, Env* env); Val* compile_integer(s64 value, Env* env); Val* compile_char(const goos::Object& code, Env* env); Val* compile_float(const goos::Object& code, Env* env); diff --git a/goalc/compiler/ConstantValue.h b/goalc/compiler/ConstantValue.h new file mode 100644 index 0000000000..c945ffd77a --- /dev/null +++ b/goalc/compiler/ConstantValue.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include "common/util/assert.h" +#include "common/common_types.h" +#include "common/util/BitUtils.h" +#include "third-party/fmt/core.h" + +struct U128 { + U128() = default; + U128(u64 _lo, u64 _hi) : lo(_lo), hi(_hi) {} + u64 lo = 0; + u64 hi = 0; +}; + +class ConstantValue { + public: + ConstantValue(const void* data, int size) : m_size(size) { + assert(size == 8 || size == 16); + memcpy(m_value, data, size); + } + + std::string print() const { + if (m_size == 8) { + return std::to_string(value_64()); + } else { + return fmt::format("0x{:08x}{:08x}", value_128_hi(), value_128_lo()); + } + } + + s64 value_64() const { + assert(m_size == 8); + s64 result; + memcpy(&result, m_value, sizeof(s64)); + return result; + } + + u64 value_128_lo() const { + assert(m_size == 16); + s64 result; + memcpy(&result, m_value, sizeof(s64)); + return result; + } + + u64 value_128_hi() const { + assert(m_size == 16); + s64 result; + memcpy(&result, m_value + sizeof(s64), sizeof(s64)); + return result; + } + + /*! + * 8 or 16 byte constant? + */ + int size() const { return m_size; } + + /*! + * Okay to put this in a gpr? + */ + bool uses_gpr() const { return m_size == 8; } + + /*! + * Try to copy data to destination. + */ + bool copy_to(void* destination, int size, bool is_signed) const { + if (m_size == 8) { + if (!integer_fits(value_64(), size, is_signed)) { + return false; + } + memcpy(destination, m_value, size); + return true; + } else if (m_size == 16) { + if (size != 16) { + return false; + } + memcpy(destination, m_value, 16); + return true; + } else { + assert(false); + } + return false; + } + + protected: + u8 m_value[16] = {0}; + int m_size; +}; \ No newline at end of file diff --git a/goalc/compiler/StaticObject.cpp b/goalc/compiler/StaticObject.cpp index e8158799d9..bed055f3db 100644 --- a/goalc/compiler/StaticObject.cpp +++ b/goalc/compiler/StaticObject.cpp @@ -180,7 +180,7 @@ void StaticPair::generate_item(const StaticResult& item, int offset) { // if it's a constant data, it should always be a boxed integer for a pair. // or I guess you could put a normal integer too. Either way, we assume signed here, // though we may need to allow overflow so you can store either signed/unsigned things in pairs - s32 value = item.get_as_s32(); + s32 value = item.constant_s32(); memcpy(data.data() + offset, &value, POINTER_SIZE); } } @@ -197,14 +197,18 @@ StaticResult StaticResult::make_structure_reference(StaticStructure* structure, return result; } -StaticResult StaticResult::make_constant_data(u64 value, TypeSpec ts) { +StaticResult StaticResult::make_constant_data(const ConstantValue& data, TypeSpec ts) { StaticResult result; result.m_kind = Kind::CONSTANT_DATA; - result.m_constant_data = value; + result.m_constant_data = data; result.m_ts = std::move(ts); return result; } +StaticResult StaticResult::make_constant_data(u64 data, const TypeSpec& ts) { + return make_constant_data(ConstantValue((void*)&data, sizeof(u64)), ts); +} + StaticResult StaticResult::make_symbol(const std::string& name) { StaticResult result; result.m_kind = Kind::SYMBOL; diff --git a/goalc/compiler/StaticObject.h b/goalc/compiler/StaticObject.h index 6cb0ead2f4..bd0910c3d9 100644 --- a/goalc/compiler/StaticObject.h +++ b/goalc/compiler/StaticObject.h @@ -1,12 +1,11 @@ #pragma once -#ifndef JAK_STATICOBJECT_H -#define JAK_STATICOBJECT_H - #include #include #include "common/type_system/TypeSpec.h" #include "goalc/emitter/ObjectGenerator.h" +#include "goalc/compiler/ConstantValue.h" +#include "common/util/BitUtils.h" class StaticObject { public: @@ -99,7 +98,8 @@ class StaticResult { StaticResult() = default; static StaticResult make_structure_reference(StaticStructure* structure, TypeSpec ts); - static StaticResult make_constant_data(u64 value, TypeSpec ts); + static StaticResult make_constant_data(const ConstantValue& data, TypeSpec ts); + static StaticResult make_constant_data(u64 data, const TypeSpec& ts); static StaticResult make_symbol(const std::string& name); std::string print() const; @@ -114,10 +114,10 @@ class StaticResult { return m_struct; } - s32 get_as_s32() const { - assert(is_constant_data()); - // todo, check that it fits. - return (s32)m_constant_data; + s32 constant_s32() const { + assert(is_constant_data() && m_constant_data && m_constant_data->size() == 8 && + integer_fits(m_constant_data->value_64(), 4, true)); + return (s32)m_constant_data->value_64(); } const std::string& symbol_name() const { @@ -125,9 +125,14 @@ class StaticResult { return m_symbol; } - u64 constant_data() const { - assert(is_constant_data()); - return m_constant_data; + u64 constant_u64() const { + assert(is_constant_data() && m_constant_data && m_constant_data->size() == 8); + return m_constant_data->value_64(); + } + + const ConstantValue& constant() const { + assert(m_constant_data.has_value()); + return *m_constant_data; } private: @@ -138,7 +143,7 @@ class StaticResult { StaticStructure* m_struct = nullptr; // used for only constant data - u64 m_constant_data = 0; + std::optional m_constant_data; // used for only symbol std::string m_symbol; @@ -156,5 +161,3 @@ class StaticPair : public StaticStructure { private: StaticResult m_car, m_cdr; }; - -#endif // JAK_STATICOBJECT_H diff --git a/goalc/compiler/Val.cpp b/goalc/compiler/Val.cpp index 0e7c0dd0b5..deb2f47620 100644 --- a/goalc/compiler/Val.cpp +++ b/goalc/compiler/Val.cpp @@ -7,7 +7,6 @@ * Fallback to_gpr if a more optimized one is not provided. */ RegVal* Val::to_gpr(Env* fe) { - // TODO - handle 128-bit stuff here! auto rv = to_reg(fe); if (rv->ireg().reg_class == RegClass::GPR_64) { return rv; @@ -90,9 +89,23 @@ const std::optional& RegVal::rlet_constraint() const { } RegVal* IntegerConstantVal::to_reg(Env* fe) { - auto rv = fe->make_gpr(coerce_to_reg_type(m_ts)); - fe->emit(std::make_unique(rv, m_value)); - return rv; + if (m_value.uses_gpr()) { + auto rv = fe->make_gpr(coerce_to_reg_type(m_ts)); + fe->emit(std::make_unique(rv, m_value.value_64())); + return rv; + } else { + auto rv = fe->make_ireg(m_ts, RegClass::INT_128); + auto gpr = fe->make_gpr(TypeSpec("object")); + auto xmm_temp = fe->make_ireg(TypeSpec("object"), RegClass::INT_128); + + fe->emit_ir(gpr, m_value.value_128_lo()); + fe->emit_ir(xmm_temp, gpr); + fe->emit_ir(gpr, m_value.value_128_hi()); + fe->emit_ir(rv, gpr); + fe->emit_ir(true, rv, rv, xmm_temp, IR_Int128Math3Asm::Kind::PCPYLD); + + return rv; + } } RegVal* SymbolVal::to_reg(Env* fe) { @@ -231,20 +244,34 @@ RegVal* StackVarAddrVal::to_reg(Env* fe) { } std::string BitFieldVal::print() const { - return fmt::format("[bitfield sz {} off {} sx {} of {}]", m_size, m_offset, m_sign_extend, - m_parent->print()); + return fmt::format("[bitfield sz {} off {} sx {} of {} 128? {}]", m_size, m_offset, m_sign_extend, + m_parent->print(), m_use_128); } RegVal* BitFieldVal::to_reg(Env* env) { - // first get the parent value - auto parent_reg = m_parent->to_gpr(env); - + int start_bit = -1; auto fe = get_parent_env_of_type(env); - auto result = fe->make_ireg(coerce_to_reg_type(m_ts), RegClass::GPR_64); - env->emit(std::make_unique(result, parent_reg)); + RegVal* result = fe->make_ireg(coerce_to_reg_type(m_ts), RegClass::GPR_64); - int start_bit = m_offset; - int end_bit = m_offset + m_size; + // this first step gets the right 64-bits into a GPR that is also used as the result. + if (m_offset < 64) { + // accessing in the lower 64 bits, we can just get the value in a GPR. + start_bit = m_offset; + RegVal* gpr = m_parent->to_gpr(env); + env->emit(std::make_unique(result, gpr)); + } else { + // we need to get the value as a 128-bit integer + auto xmm = m_parent->to_reg(env); + assert(xmm->ireg().reg_class == RegClass::INT_128); + auto xmm_temp = fe->make_ireg(TypeSpec("object"), RegClass::INT_128); + env->emit_ir(true, xmm_temp, xmm, xmm, IR_Int128Math3Asm::Kind::PCPYUD); + env->emit_ir(result, xmm_temp); + start_bit = m_offset - 64; + } + + // this second step does up to 2 shifts to extract the bitfield and sign extend as needed. + int end_bit = start_bit + m_size; + assert(end_bit <= 64); // should be checked by the type system. int epad = 64 - end_bit; assert(epad >= 0); int spad = start_bit; diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index f6423fdcee..40bd97c5b4 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -5,9 +5,6 @@ * The GOAL Value. A value represents a place (where the value is stored) and a type. */ -#ifndef JAK_VAL_H -#define JAK_VAL_H - #include #include #include @@ -16,6 +13,7 @@ #include "goalc/regalloc/IRegister.h" #include "Lambda.h" #include "StaticObject.h" +#include "goalc/compiler/ConstantValue.h" class RegVal; class Env; @@ -240,13 +238,17 @@ class AliasVal : public Val { class IntegerConstantVal : public Val { public: - IntegerConstantVal(TypeSpec ts, s64 value) : Val(std::move(ts)), m_value(value) {} - std::string print() const override { return "integer-constant-" + std::to_string(m_value); } + IntegerConstantVal(TypeSpec ts, const void* data, int size) + : Val(std::move(ts)), m_value(data, size) { + assert(size == 8 || size == 16); + } + + std::string print() const override { return std::string("integer-constant-") + m_value.print(); } RegVal* to_reg(Env* fe) override; - s64 value() const { return m_value; } + const ConstantValue& value() const { return m_value; } protected: - s64 m_value = -1; + ConstantValue m_value; }; class FloatConstantVal : public Val { @@ -261,12 +263,13 @@ class FloatConstantVal : public Val { class BitFieldVal : public Val { public: - BitFieldVal(TypeSpec ts, Val* parent, int offset, int size, bool sign_extend) + BitFieldVal(TypeSpec ts, Val* parent, int offset, int size, bool sign_extend, bool use128) : Val(std::move(ts)), m_parent(parent), m_offset(offset), m_size(size), - m_sign_extend(sign_extend) { + m_sign_extend(sign_extend), + m_use_128(use128) { m_is_settable = parent->settable(); } std::string print() const override; @@ -274,6 +277,7 @@ class BitFieldVal : public Val { int offset() const { return m_offset; } int size() const { return m_size; } bool sext() const { return m_sign_extend; } + bool use_128_bit() const { return m_use_128; } Val* parent() { return m_parent; } protected: @@ -281,6 +285,5 @@ class BitFieldVal : public Val { int m_offset = -1; int m_size = -1; bool m_sign_extend = false; + bool m_use_128 = false; }; - -#endif // JAK_VAL_H diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 22612b50df..95393fc1d8 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -308,7 +308,12 @@ Val* Compiler::compile_char(const goos::Object& code, Env* env) { */ Val* Compiler::compile_integer(s64 value, Env* env) { auto fe = get_parent_env_of_type(env); - return fe->alloc_val(m_ts.make_typespec("int"), value); + return fe->alloc_val(m_ts.make_typespec("int"), &value, 8); +} + +Val* Compiler::compile_integer(const U128& value, Env* env) { + auto fe = get_parent_env_of_type(env); + return fe->alloc_val(m_ts.make_typespec("int"), &value, 16); } /*! diff --git a/goalc/compiler/compilation/Define.cpp b/goalc/compiler/compilation/Define.cpp index dd952d2dbd..2399645c52 100644 --- a/goalc/compiler/compilation/Define.cpp +++ b/goalc/compiler/compilation/Define.cpp @@ -99,30 +99,29 @@ 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) { - 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)); - +/*! + * Modify dst by setting the bitfield with give size/offset to the value in src. + */ +void Compiler::set_bits_in_bitfield(int size, + int offset, + RegVal* dst, + RegVal* src, + FunctionEnv* fe, + Env* env) { // 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()); + u64 mask_val = ~((((u64)1 << (u64)size) - (u64)1) << (u64)offset); env->emit(std::make_unique(temp, mask_val)); // modify the original! - env->emit(std::make_unique(IntegerMathKind::AND_64, original, temp)); + env->emit(std::make_unique(IntegerMathKind::AND_64, dst, 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(); + int left_shift_amnt = 64 - size; + int right_shift_amnt = (64 - size) - offset; assert(right_shift_amnt >= 0); if (left_shift_amnt > 0) { @@ -133,10 +132,72 @@ void Compiler::set_bitfield(const goos::Object& form, BitFieldVal* dst, RegVal* env->emit(std::make_unique(IntegerMathKind::SHR_64, temp, right_shift_amnt)); } - env->emit(std::make_unique(IntegerMathKind::OR_64, original, temp)); + env->emit(std::make_unique(IntegerMathKind::OR_64, dst, temp)); +} + +void Compiler::set_bitfield(const goos::Object& form, BitFieldVal* dst, RegVal* src, Env* env) { + if (dst->use_128_bit()) { + set_bitfield_128(form, dst, src, env); + return; + } + + 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)); + set_bits_in_bitfield(dst->size(), dst->offset(), original, src, fe, env); + do_set(form, dst->parent(), original, original, env); } +void Compiler::set_bitfield_128(const goos::Object& form, BitFieldVal* dst, RegVal* src, Env* env) { + auto fe = get_parent_env_of_type(env); + + bool get_top = dst->offset() >= 64; + + // first, get the value we want to modify: + assert(m_ts.lookup_type(dst->parent()->type())->get_preferred_reg_class() == RegClass::INT_128); + RegVal* original_original = dst->parent()->to_xmm128(env); + + // next, get the 64-bit part we want to modify in the lower 64 bits of an XMM + RegVal* xmm_temp = fe->make_ireg(original_original->type(), RegClass::INT_128); + if (get_top) { + env->emit_ir(true, xmm_temp, original_original, original_original, + IR_Int128Math3Asm::Kind::PCPYUD); + } else { + env->emit_ir(xmm_temp, original_original); + } + + // convert that xmm to a GPR. + RegVal* gpr_64_section = fe->make_gpr(original_original->type()); + env->emit_ir(gpr_64_section, xmm_temp); + + // set the bits in the GPR + int corrected_offset = get_top ? dst->offset() - 64 : dst->offset(); + set_bits_in_bitfield(dst->size(), corrected_offset, gpr_64_section, src, fe, env); + + // back to xmm + env->emit_ir(xmm_temp, gpr_64_section); + + // rebuild the xmm + if (get_top) { + env->emit_ir(true, xmm_temp, xmm_temp, original_original, + IR_Int128Math3Asm::Kind::PCPYLD); + } else { + env->emit_ir(true, xmm_temp, xmm_temp, xmm_temp, + IR_Int128Math3Asm::Kind::PCPYLD); + env->emit_ir(true, xmm_temp, xmm_temp, original_original, + IR_Int128Math3Asm::Kind::PCPYUD); + } + + // set + do_set(form, dst->parent(), xmm_temp, xmm_temp, env); +} + /*! * The internal "set" logic. * The source is provided both as the directly Val* from compilation and as a RegVal*. @@ -187,7 +248,7 @@ Val* Compiler::do_set(const goos::Object& form, Val* dest, RegVal* src_in_reg, V src_in_reg, base_as_mco->offset, base_as_mco->base->to_gpr(env), ti->get_load_size())); return src_in_reg; } else { - // nope, the pointer to dereference is some compliated thing. + // nope, the pointer to dereference is some complicated thing. auto ti = m_ts.lookup_type(as_mem_deref->type()); env->emit(std::make_unique(src_in_reg, 0, base->to_gpr(env), ti->get_load_size())); diff --git a/goalc/compiler/compilation/Math.cpp b/goalc/compiler/compilation/Math.cpp index cfa564303a..7a6dbe494e 100644 --- a/goalc/compiler/compilation/Math.cpp +++ b/goalc/compiler/compilation/Math.cpp @@ -171,8 +171,8 @@ Val* Compiler::compile_mul(const goos::Object& form, const goos::Object& rest, E auto val = compile_error_guard(args.unnamed.at(i), env); auto val_as_int = dynamic_cast(val); int power_of_two = -1; - if (val_as_int && val_as_int->value() > 0) { - auto p = get_power_of_two(val_as_int->value()); + if (val_as_int && val_as_int->value().uses_gpr() && val_as_int->value().value_64() > 0) { + auto p = get_power_of_two(val_as_int->value().value_64()); if (p) { power_of_two = *p; } @@ -390,8 +390,8 @@ Val* Compiler::compile_div(const goos::Object& form, const goos::Object& rest, E auto val = compile_error_guard(args.unnamed.at(1), env); auto val_as_int = dynamic_cast(val); int power_of_two = -1; - if (val_as_int && val_as_int->value() > 0) { - auto p = get_power_of_two(val_as_int->value()); + if (val_as_int && val_as_int->value().uses_gpr() && val_as_int->value().value_64() > 0) { + auto p = get_power_of_two(val_as_int->value().value_64()); if (p) { power_of_two = *p; } diff --git a/goalc/compiler/compilation/Static.cpp b/goalc/compiler/compilation/Static.cpp index 7787650098..495772c1b5 100644 --- a/goalc/compiler/compilation/Static.cpp +++ b/goalc/compiler/compilation/Static.cpp @@ -8,42 +8,6 @@ #include "third-party/fmt/core.h" #include "common/goos/ParseHelpers.h" -namespace { -bool integer_fits(s64 in, int size, bool is_signed) { - switch (size) { - case 1: - if (is_signed) { - return in >= INT8_MIN && in <= INT8_MAX; - } else { - return in >= 0 && in <= UINT8_MAX; - } - case 2: - if (is_signed) { - return in >= INT16_MIN && in <= INT16_MAX; - } else { - return in >= 0 && in <= UINT16_MAX; - } - case 4: - if (is_signed) { - return in >= INT32_MIN && in <= INT32_MAX; - } else { - return in >= 0 && in <= UINT32_MAX; - } - case 8: - return true; - default: - assert(false); - return false; - } -} - -u32 float_as_u32(float x) { - u32 result; - memcpy(&result, &x, 4); - return result; -} -} // namespace - /*! * Compile the fields of a static structure into the given StaticStructure*, applying an offset. * This can be used to generate an entire structure (set offset to 0), or to fill out an inline @@ -159,10 +123,8 @@ void Compiler::compile_static_structure_inline(const goos::Object& form, } else if (is_integer(field_info.type)) { assert(field_info.needs_deref); // for now... auto deref_info = m_ts.get_deref_info(m_ts.make_pointer_typespec(field_info.type)); - auto field_size = deref_info.load_size; - assert(field_offset + field_size <= int(structure->data.size())); + assert(field_offset + deref_info.load_size <= int(structure->data.size())); assert(!field_info.field.is_inline()); - s64 value = 0; auto sr = compile_static(field_value, env); if (!sr.is_constant_data()) { throw_compiler_error(form, "Could not use {} for an integer field", field_value.print()); @@ -170,23 +132,17 @@ void Compiler::compile_static_structure_inline(const goos::Object& form, // we are not strict with the type checking here, as long as you give an "integer" and it // ends up fitting, it's okay. typecheck(form, TypeSpec("integer"), sr.typespec()); - value = sr.constant_data(); - if (!integer_fits(value, deref_info.load_size, deref_info.sign_extend)) { + if (!sr.constant().copy_to(structure->data.data() + field_offset, deref_info.load_size, + deref_info.sign_extend)) { throw_compiler_error(form, "Field {} is set to a compile time integer value of {} which would " "overflow (size {} signed {})", - field_name_def, value, deref_info.load_size, deref_info.sign_extend); + field_name_def, sr.constant().print(), deref_info.load_size, + deref_info.sign_extend); } - if (field_size == 1 || field_size == 2 || field_size == 4 || field_size == 8) { - memcpy(structure->data.data() + field_offset, &value, field_size); - } else { - // not sure how we can create 128-bit integer constants at this point... - assert(false); - } } else if (is_structure(field_info.type) || is_pair(field_info.type)) { - // todo - rewrite this to correctly handle structures within structures. if (is_pair(field_info.type)) { assert(!field_info.field.is_inline()); } @@ -264,7 +220,7 @@ void Compiler::compile_static_structure_inline(const goos::Object& form, throw_compiler_error(form, "Could not use {} for a float field", field_value.print()); } typecheck(form, TypeSpec("float"), sr.typespec()); - u64 value = sr.constant_data(); + u64 value = sr.constant_u64(); memcpy(structure->data.data() + field_offset, &value, sizeof(float)); } @@ -311,12 +267,12 @@ Val* Compiler::compile_bitfield_definition(const goos::Object& form, bool allow_dynamic_construction, Env* env) { // unset fields are 0, so initialize our constant value to 0 here. - u64 constant_integer_part = 0; + U128 constant_integer_part; // look up the bitfield type we're working with. For now, make sure it's under 8 bytes. auto type_info = dynamic_cast(m_ts.lookup_type(type)); assert(type_info); - assert(type_info->get_load_size() <= 8); + bool use_128 = type_info->get_load_size() == 16; // We will construct this bitfield in two passes. // The first pass loops through definitions. If they can be evaluated at compile time, it adds @@ -378,6 +334,7 @@ Val* Compiler::compile_bitfield_definition(const goos::Object& form, } else { u64 unsigned_value = value; u64 or_value = unsigned_value; + assert(field_size <= 64); // shift us all the way left to clear upper bits. or_value <<= (64 - field_size); // and back right. @@ -386,7 +343,14 @@ Val* Compiler::compile_bitfield_definition(const goos::Object& form, throw_compiler_error(form, "Field {}'s value doesn't fit.", field_name_def); } - constant_integer_part |= (or_value << field_offset); + bool start_lo = field_offset < 64; + bool end_lo = (field_offset + field_size) <= 64; + assert(start_lo == end_lo); + if (end_lo) { + constant_integer_part.lo |= (or_value << field_offset); + } else { + constant_integer_part.hi |= (or_value << (field_offset - 64)); + } } } else if (is_float(field_info.result_type)) { @@ -404,7 +368,14 @@ Val* Compiler::compile_bitfield_definition(const goos::Object& form, field_name_def); } u64 float_value = float_as_u32(value); - constant_integer_part |= (float_value << field_offset); + bool start_lo = field_offset < 64; + bool end_lo = (field_offset + field_size) <= 64; + assert(start_lo == end_lo); + if (end_lo) { + constant_integer_part.lo |= (float_value << field_offset); + } else { + constant_integer_part.hi |= (float_value << (field_offset - 64)); + } } else { @@ -413,39 +384,97 @@ Val* Compiler::compile_bitfield_definition(const goos::Object& form, } } + Val* integer; + if (use_128) { + integer = compile_integer(constant_integer_part, env); + } else { + integer = compile_integer(constant_integer_part.lo, env); + assert(constant_integer_part.hi == 0); + } + + integer->set_type(type); + if (dynamic_defs.empty()) { - auto integer = compile_integer(constant_integer_part, env); - integer->set_type(type); return integer; } else { assert(allow_dynamic_construction); - auto integer = compile_integer(constant_integer_part, env)->to_gpr(env); - for (auto& def : dynamic_defs) { - auto field_val = compile_error_guard(def.definition, env)->to_gpr(env); - if (!m_ts.tc(def.expected_type, field_val->type())) { - throw_compiler_error(form, "Typecheck failed for bitfield {}! Got a {} but expected a {}", - def.field_name, field_val->type().print(), def.expected_type.print()); - } - int left_shift_amnt = 64 - def.field_size; - int right_shift_amnt = (64 - def.field_size) - def.field_offset; - assert(right_shift_amnt >= 0); + if (use_128) { + auto integer_lo = compile_integer(constant_integer_part.lo, env)->to_gpr(env); + auto integer_hi = compile_integer(constant_integer_part.hi, env)->to_gpr(env); + auto fe = get_parent_env_of_type(env); + auto rv = fe->make_ireg(type, RegClass::INT_128); + auto xmm_temp = fe->make_ireg(TypeSpec("object"), RegClass::INT_128); - if (left_shift_amnt > 0) { - env->emit( - std::make_unique(IntegerMathKind::SHL_64, field_val, left_shift_amnt)); + for (auto& def : dynamic_defs) { + auto field_val = compile_error_guard(def.definition, env)->to_gpr(env); + if (!m_ts.tc(def.expected_type, field_val->type())) { + throw_compiler_error(form, "Typecheck failed for bitfield {}! Got a {} but expected a {}", + def.field_name, field_val->type().print(), + def.expected_type.print()); + } + + bool start_lo = def.field_offset < 64; + bool end_lo = def.field_offset + def.field_size <= 64; + assert(start_lo == end_lo); + assert(def.field_size <= 64); + + int corrected_offset = def.field_offset; + if (!start_lo) { + corrected_offset -= 64; + } + + int left_shift_amnt = 64 - def.field_size; + int right_shift_amnt = (64 - def.field_size) - corrected_offset; + assert(right_shift_amnt >= 0); + + if (left_shift_amnt > 0) { + env->emit(std::make_unique(IntegerMathKind::SHL_64, field_val, + left_shift_amnt)); + } + + if (right_shift_amnt > 0) { + env->emit(std::make_unique(IntegerMathKind::SHR_64, field_val, + right_shift_amnt)); + } + + env->emit(std::make_unique(IntegerMathKind::OR_64, + start_lo ? integer_lo : integer_hi, field_val)); } - if (right_shift_amnt > 0) { - env->emit( - std::make_unique(IntegerMathKind::SHR_64, field_val, right_shift_amnt)); + fe->emit_ir(xmm_temp, integer_lo); + fe->emit_ir(rv, integer_hi); + fe->emit_ir(true, rv, rv, xmm_temp, IR_Int128Math3Asm::Kind::PCPYLD); + return rv; + } else { + RegVal* integer_reg = integer->to_gpr(env); + for (auto& def : dynamic_defs) { + auto field_val = compile_error_guard(def.definition, env)->to_gpr(env); + if (!m_ts.tc(def.expected_type, field_val->type())) { + throw_compiler_error(form, "Typecheck failed for bitfield {}! Got a {} but expected a {}", + def.field_name, field_val->type().print(), + def.expected_type.print()); + } + int left_shift_amnt = 64 - def.field_size; + int right_shift_amnt = (64 - def.field_size) - def.field_offset; + assert(right_shift_amnt >= 0); + + if (left_shift_amnt > 0) { + env->emit(std::make_unique(IntegerMathKind::SHL_64, field_val, + left_shift_amnt)); + } + + if (right_shift_amnt > 0) { + env->emit(std::make_unique(IntegerMathKind::SHR_64, field_val, + right_shift_amnt)); + } + + env->emit(std::make_unique(IntegerMathKind::OR_64, integer_reg, field_val)); } - env->emit(std::make_unique(IntegerMathKind::OR_64, integer, field_val)); + integer_reg->set_type(type); + return integer_reg; } - - integer->set_type(type); - return integer; } } @@ -609,7 +638,6 @@ StaticResult Compiler::compile_static(const goos::Object& form_before_macro, Env } else if (is_bitfield(ts)) { auto val = dynamic_cast( compile_bitfield_definition(form, ts, constructor_args, false, env)); - assert(val); return StaticResult::make_constant_data(val->value(), val->type()); } else if (is_structure(ts)) { return compile_new_static_structure(form, ts, constructor_args, env); @@ -682,12 +710,11 @@ void Compiler::fill_static_array_inline(const goos::Object& form, assert(deref_info.stride == 4); structure->add_pointer_record(elt_offset, sr.reference(), sr.reference()->get_addr_offset()); } else if (sr.is_constant_data()) { - if (!integer_fits(sr.constant_data(), deref_info.load_size, deref_info.sign_extend)) { + if (!sr.constant().copy_to(structure->data.data() + elt_offset, deref_info.load_size, + deref_info.sign_extend)) { throw_compiler_error(form, "The integer {} doesn't fit in element {} of array of {}", - sr.constant_data(), arg_idx, content_type.print()); + sr.constant().print(), arg_idx, content_type.print()); } - u64 data = sr.constant_data(); - memcpy(structure->data.data() + elt_offset, &data, deref_info.load_size); } else { assert(false); } diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index cffa60f00f..43b4654e40 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -505,7 +505,8 @@ Val* Compiler::get_field_of_bitfield(const BitFieldType* type, Val* result = nullptr; auto bitfield_info = m_ts.lookup_bitfield_info(type->get_name(), field_name); result = fe->alloc_val(bitfield_info.result_type, object, bitfield_info.offset, - bitfield_info.size, bitfield_info.sign_extend); + bitfield_info.size, bitfield_info.sign_extend, + type->get_load_size() == 16); return result; } @@ -582,7 +583,8 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest 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); + bitfield_info.size, bitfield_info.sign_extend, + type_info->get_load_size() == 16); continue; } } @@ -745,7 +747,7 @@ Val* Compiler::compile_print_type(const goos::Object& form, const goos::Object& auto args = get_va(form, rest); va_check(form, args, {{}}, {}); auto result = compile(args.unnamed.at(0), env)->to_reg(env); - fmt::print("[TYPE] {}\n", result->type().print()); + fmt::print("[TYPE] {} {}\n", result->type().print(), result->print()); return result; } @@ -1107,7 +1109,7 @@ u64 Compiler::enum_lookup(const goos::Object& form, return; } } - value |= (1 << kv->second); + value |= ((u64)1 << (u64)kv->second); }); return value; diff --git a/test/goalc/source_templates/with_game/test-access-bitfield128.gc b/test/goalc/source_templates/with_game/test-access-bitfield128.gc new file mode 100644 index 0000000000..1689d682bf --- /dev/null +++ b/test/goalc/source_templates/with_game/test-access-bitfield128.gc @@ -0,0 +1,95 @@ +(deftype b128 (uint128) + ((f1 int32 :offset 0) + (f2 int32 :offset 32) + (f3 int32 :offset 64) + (f4 int32 :offset 96)) + ) + +(deftype b128-structure (structure) + ((val b128)) + ) + + + (let ((val (the b128 (make-u128 #xabcdbeef77777777 #x6666666612347890))) + (struct (new 'stack 'b128-structure)) + ) + (set! (-> struct val) val) + (format #t "~8x ~8x ~8x ~8x~%" + (-> struct val f4) + (-> struct val f3) + (-> struct val f2) + (-> struct val f1) + ) + (set! (-> struct val f1) 1) + (format #t "~8x ~8x ~8x ~8x~%" + (-> struct val f4) + (-> struct val f3) + (-> struct val f2) + (-> struct val f1) + ) + (set! (-> struct val f2) 2) + (format #t "~8x ~8x ~8x ~8x~%" + (-> struct val f4) + (-> struct val f3) + (-> struct val f2) + (-> struct val f1) + ) + (set! (-> struct val f3) 3) + (format #t "~8x ~8x ~8x ~8x~%" + (-> struct val f4) + (-> struct val f3) + (-> struct val f2) + (-> struct val f1) + ) + (set! (-> struct val f4) 4) + (format #t "~8x ~8x ~8x ~8x~%" + (-> struct val f4) + (-> struct val f3) + (-> struct val f2) + (-> struct val f1) + ) + ) + +(let ((static-structure (new 'static 'b128-structure + :val (new 'static 'b128 + :f1 #x12341234 + :f2 #x7 + :f3 #x666 + :f4 #xdeadbeef)))) + (format #t "~8x ~8x ~8x ~8x~%" + (-> static-structure val f1) + (-> static-structure val f2) + (-> static-structure val f3) + (-> static-structure val f4)) + ) + +(let ((val (new 'static 'b128 + :f1 #x23232323 + :f2 #x78787878 + :f3 #x92929292 + :f4 #x12124545)) + ) + (format #t "~8x ~8x ~8x ~8x~%" + (-> val f4) + (-> val f3) + (-> val f2) + (-> val f1) + ) + ) + +(let* ((f1 #xffffffff00001212) + (f4 #xffffffff00009878) + (val (new 'static 'b128 + :f1 f1 + :f2 #x2222 + :f3 #x3333 + :f4 f4 + ))) + (format #t "~8x ~8x ~8x ~8x~%" + (-> val f4) + (-> val f3) + (-> val f2) + (-> val f1) + ) + ) + diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index b184e741ff..2b09ca8100 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -365,6 +365,19 @@ TEST_F(WithGameTests, TrickyBitField) { get_test_pass_string("bitfield-tricky-access", 14)); } +TEST_F(WithGameTests, Bitfield128) { + runner.run_static_test(env, testCategory, "test-access-bitfield128.gc", + {"-abcdbeef 77777777 66666666 12347890\n" + "-abcdbeef 77777777 66666666 00000001\n" + "-abcdbeef 77777777 00000002 00000001\n" + "-abcdbeef 00000003 00000002 00000001\n" + "00000004 00000003 00000002 00000001\n" + "12341234 00000007 00000666 -deadbeef\n" + "12124545 -92929292 78787878 23232323\n" + "00009878 00003333 00002222 00001212\n" + "0\n"}); +} + TEST_F(WithGameTests, Math) { runner.run_static_test(env, testCategory, "test-math.gc", get_test_pass_string("math", 31)); }