/*! * @file Static.cpp * Compiler helper functions for creating static data. * This is the front end for things in StaticObject.cpp */ #include "goalc/compiler/Compiler.h" #include "third-party/fmt/core.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); } } 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_compiler_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_compiler_error(form, "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_compiler_error(form, "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_compiler_error(form, "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_compiler_error(form, "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, Env* env) { auto fe = get_parent_env_of_type(env); std::unique_ptr obj; if (is_basic(type)) { obj = std::make_unique(MAIN_SEGMENT, type.base_type()); } else { // if we ever find this type of static data outside of MAIN_SEGMENT, we can create an option // in the new form to pick the segment. obj = std::make_unique(MAIN_SEGMENT); } auto type_info = dynamic_cast(m_ts.lookup_type(type)); assert(type_info); // should always be at least a structure. obj->data.resize(type_info->get_size_in_memory()); // the file env will end up owning the obj. auto result = fe->alloc_val(obj.get(), type); 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_compiler_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_field_info(type_info->get_name(), field_name_def); if (field_info.field.is_dynamic() || field_info.field.is_inline() || field_info.field.is_array()) { throw_compiler_error(form, "Static objects not yet implemented for dynamic/inline/array"); } auto field_offset = field_info.field.offset(); 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(obj->data.size())); if (is_integer(field_info.type)) { s64 value = 0; if (!try_getting_constant_integer(field_value, &value, env)) { throw_compiler_error(form, "Field {} is an integer, but the value given couldn't be " "converted to an integer at compile time.", field_name_def); } if (!integer_fits(value, 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); } if (field_size == 1 || field_size == 2 || field_size == 4 || field_size == 8) { memcpy(obj->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_basic(field_info.type)) { if (is_quoted_sym(field_value)) { obj->add_symbol_record(quoted_sym_as_string(field_value), field_offset); assert(deref_info.mem_deref); assert(deref_info.can_deref); assert(deref_info.load_size == 4); // the linker needs to see a -1 in order to know to insert a symbol pointer // instead of just the symbol table offset. u32 linker_val = 0xffffffff; memcpy(obj->data.data() + field_offset, &linker_val, 4); } else if (field_value.is_symbol() && (field_value.as_symbol()->name == "#t" || field_value.as_symbol()->name == "#f")) { obj->add_symbol_record(symbol_string(field_value), field_offset); assert(deref_info.mem_deref); assert(deref_info.can_deref); assert(deref_info.load_size == 4); // the linker needs to see a -1 in order to know to insert a symbol pointer // instead of just the symbol table offset. u32 linker_val = 0xffffffff; memcpy(obj->data.data() + field_offset, &linker_val, 4); } else { throw_compiler_error( form, "Setting a basic field to anything other than a symbol is currently unsupported"); } } else if (is_float(field_info.type)) { float value = 0.f; if (!try_getting_constant_float(field_value, &value, env)) { throw_compiler_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)); } memcpy(obj->data.data() + field_offset, &value, sizeof(float)); } else { assert(false); // for now } } auto fie = get_parent_env_of_type(env); fie->add_static(std::move(obj)); return result; }