[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
This commit is contained in:
water111 2021-05-18 21:25:29 -04:00 committed by GitHub
parent ec412c7777
commit a6258f3654
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 548 additions and 178 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -475,6 +475,9 @@ DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts) {
assert(pto);
auto new_type = std::make_unique<BitFieldType>(
parent_type_name, name, pto->get_size_in_memory(), pto->get_load_signed());
auto parent_value = dynamic_cast<ValueType*>(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;

37
common/util/BitUtils.cpp Normal file
View File

@ -0,0 +1,37 @@
#include "BitUtils.h"
#include <cstring>
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;
}

View File

@ -2,8 +2,8 @@
#include <optional>
#include "common/util/assert.h"
#include "common/util/Range.h"
#include "common/common_types.h"
constexpr int BITS_PER_BYTE = 8;
template <typename T>
@ -60,4 +60,7 @@ std::optional<int> get_power_of_two(T in) {
} else {
return std::nullopt;
}
}
}
bool integer_fits(s64 in, int size, bool is_signed);
u32 float_as_u32(float x);

View File

@ -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).
- 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

View File

@ -40,6 +40,7 @@
(water-tex1 60)
;; merc1 61
;; generic1 62
;; debug text 68
)
;; Information related to visibility data for a level.

View File

@ -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);

View File

@ -0,0 +1,87 @@
#pragma once
#include <string>
#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;
};

View File

@ -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;

View File

@ -1,12 +1,11 @@
#pragma once
#ifndef JAK_STATICOBJECT_H
#define JAK_STATICOBJECT_H
#include <string>
#include <vector>
#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<ConstantValue> 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

View File

@ -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<emitter::Register>& 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<IR_LoadConstant64>(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<IR_LoadConstant64>(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<IR_LoadConstant64>(gpr, m_value.value_128_lo());
fe->emit_ir<IR_RegSet>(xmm_temp, gpr);
fe->emit_ir<IR_LoadConstant64>(gpr, m_value.value_128_hi());
fe->emit_ir<IR_RegSet>(rv, gpr);
fe->emit_ir<IR_Int128Math3Asm>(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<FunctionEnv>(env);
auto result = fe->make_ireg(coerce_to_reg_type(m_ts), RegClass::GPR_64);
env->emit(std::make_unique<IR_RegSet>(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<IR_RegSet>(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<IR_Int128Math3Asm>(true, xmm_temp, xmm, xmm, IR_Int128Math3Asm::Kind::PCPYUD);
env->emit_ir<IR_RegSet>(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;

View File

@ -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 <utility>
#include <string>
#include <stdexcept>
@ -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

View File

@ -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<FunctionEnv>(env);
return fe->alloc_val<IntegerConstantVal>(m_ts.make_typespec("int"), value);
return fe->alloc_val<IntegerConstantVal>(m_ts.make_typespec("int"), &value, 8);
}
Val* Compiler::compile_integer(const U128& value, Env* env) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
return fe->alloc_val<IntegerConstantVal>(m_ts.make_typespec("int"), &value, 16);
}
/*!

View File

@ -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<FunctionEnv>(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<IR_RegSet>(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<IR_LoadConstant64>(temp, mask_val));
// modify the original!
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::AND_64, original, temp));
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::AND_64, dst, temp));
// put the source in temp
env->emit(std::make_unique<IR_RegSet>(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<IR_IntegerMath>(IntegerMathKind::SHR_64, temp, right_shift_amnt));
}
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::OR_64, original, temp));
env->emit(std::make_unique<IR_IntegerMath>(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<FunctionEnv>(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<IR_RegSet>(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<FunctionEnv>(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<IR_Int128Math3Asm>(true, xmm_temp, original_original, original_original,
IR_Int128Math3Asm::Kind::PCPYUD);
} else {
env->emit_ir<IR_RegSet>(xmm_temp, original_original);
}
// convert that xmm to a GPR.
RegVal* gpr_64_section = fe->make_gpr(original_original->type());
env->emit_ir<IR_RegSet>(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<IR_RegSet>(xmm_temp, gpr_64_section);
// rebuild the xmm
if (get_top) {
env->emit_ir<IR_Int128Math3Asm>(true, xmm_temp, xmm_temp, original_original,
IR_Int128Math3Asm::Kind::PCPYLD);
} else {
env->emit_ir<IR_Int128Math3Asm>(true, xmm_temp, xmm_temp, xmm_temp,
IR_Int128Math3Asm::Kind::PCPYLD);
env->emit_ir<IR_Int128Math3Asm>(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<IR_StoreConstOffset>(src_in_reg, 0, base->to_gpr(env),
ti->get_load_size()));

View File

@ -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<IntegerConstantVal*>(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<IntegerConstantVal*>(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;
}

View File

@ -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<BitFieldType*>(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<FunctionEnv>(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<IR_IntegerMath>(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<IR_IntegerMath>(IntegerMathKind::SHL_64, field_val,
left_shift_amnt));
}
if (right_shift_amnt > 0) {
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::SHR_64, field_val,
right_shift_amnt));
}
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::OR_64,
start_lo ? integer_lo : integer_hi, field_val));
}
if (right_shift_amnt > 0) {
env->emit(
std::make_unique<IR_IntegerMath>(IntegerMathKind::SHR_64, field_val, right_shift_amnt));
fe->emit_ir<IR_RegSet>(xmm_temp, integer_lo);
fe->emit_ir<IR_RegSet>(rv, integer_hi);
fe->emit_ir<IR_Int128Math3Asm>(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<IR_IntegerMath>(IntegerMathKind::SHL_64, field_val,
left_shift_amnt));
}
if (right_shift_amnt > 0) {
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::SHR_64, field_val,
right_shift_amnt));
}
env->emit(std::make_unique<IR_IntegerMath>(IntegerMathKind::OR_64, integer_reg, field_val));
}
env->emit(std::make_unique<IR_IntegerMath>(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<const IntegerConstantVal*>(
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);
}

View File

@ -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<BitFieldVal>(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<BitFieldVal>(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;

View File

@ -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)
)
)

View File

@ -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));
}