mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 11:26:18 -04:00
573 lines
20 KiB
C++
573 lines
20 KiB
C++
#include "bitfields.h"
|
|
|
|
#include "decompiler/IR2/Form.h"
|
|
#include "common/goos/PrettyPrinter.h"
|
|
#include "common/util/Range.h"
|
|
#include "common/util/BitUtils.h"
|
|
#include "decompiler/util/DecompilerTypeSystem.h"
|
|
#include "decompiler/IR2/GenericElementMatcher.h"
|
|
|
|
namespace decompiler {
|
|
|
|
////////////////////////////////
|
|
// BitfieldStaticDefElement
|
|
////////////////////////////////
|
|
|
|
BitfieldStaticDefElement::BitfieldStaticDefElement(const TypeSpec& type,
|
|
const std::vector<BitFieldDef>& field_defs)
|
|
: m_type(type), m_field_defs(field_defs) {
|
|
for (auto& x : field_defs) {
|
|
x.value->parent_element = this;
|
|
}
|
|
}
|
|
|
|
BitfieldStaticDefElement::BitfieldStaticDefElement(
|
|
const TypeSpec& type,
|
|
const std::vector<BitFieldConstantDef>& field_defs,
|
|
FormPool& pool)
|
|
: m_type(type) {
|
|
for (auto& x : field_defs) {
|
|
m_field_defs.push_back(BitFieldDef::from_constant(x, pool));
|
|
}
|
|
}
|
|
|
|
goos::Object BitfieldStaticDefElement::to_form_internal(const Env& env) const {
|
|
std::vector<goos::Object> result;
|
|
|
|
result.push_back(pretty_print::to_symbol(fmt::format("new 'static '{}", m_type.print())));
|
|
|
|
for (auto& def : m_field_defs) {
|
|
auto def_as_atom = form_as_atom(def.value);
|
|
if (def_as_atom && def_as_atom->is_int()) {
|
|
u64 v = def_as_atom->get_int();
|
|
if (def.is_signed) {
|
|
result.push_back(pretty_print::to_symbol(fmt::format(":{} {}", def.field_name, (s64)v)));
|
|
} else {
|
|
result.push_back(pretty_print::to_symbol(fmt::format(":{} #x{:x}", def.field_name, v)));
|
|
}
|
|
} else {
|
|
// TODO: will this make ugly massive lines?
|
|
result.push_back(pretty_print::to_symbol(
|
|
fmt::format(":{} {}", def.field_name, def.value->to_string(env))));
|
|
}
|
|
}
|
|
|
|
return pretty_print::build_list(result);
|
|
}
|
|
|
|
void BitfieldStaticDefElement::apply(const std::function<void(FormElement*)>& f) {
|
|
f(this);
|
|
for (auto& x : m_field_defs) {
|
|
x.value->apply(f);
|
|
}
|
|
}
|
|
|
|
void BitfieldStaticDefElement::apply_form(const std::function<void(Form*)>& f) {
|
|
for (auto& x : m_field_defs) {
|
|
x.value->apply_form(f);
|
|
}
|
|
}
|
|
void BitfieldStaticDefElement::collect_vars(RegAccessSet& vars, bool recursive) const {
|
|
if (recursive) {
|
|
for (auto& x : m_field_defs) {
|
|
x.value->collect_vars(vars, recursive);
|
|
}
|
|
}
|
|
}
|
|
void BitfieldStaticDefElement::get_modified_regs(RegSet& regs) const {
|
|
for (auto& x : m_field_defs) {
|
|
x.value->get_modified_regs(regs);
|
|
}
|
|
}
|
|
|
|
ModifiedCopyBitfieldElement::ModifiedCopyBitfieldElement(
|
|
const TypeSpec& type,
|
|
Form* base,
|
|
const std::vector<BitFieldDef>& field_modifications)
|
|
: m_type(type), m_base(base), m_field_modifications(field_modifications) {
|
|
m_base->parent_element = this;
|
|
for (auto& mod : m_field_modifications) {
|
|
if (mod.value) {
|
|
mod.value->parent_element = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
goos::Object ModifiedCopyBitfieldElement::to_form_internal(const Env& env) const {
|
|
std::vector<goos::Object> result = {pretty_print::to_symbol("copy-and-set-bf")};
|
|
result.push_back(m_base->to_form(env));
|
|
for (auto& def : m_field_modifications) {
|
|
result.push_back(pretty_print::to_symbol(fmt::format(":{}", def.field_name)));
|
|
result.push_back(def.value->to_form(env));
|
|
}
|
|
return pretty_print::build_list(result);
|
|
}
|
|
|
|
void ModifiedCopyBitfieldElement::apply(const std::function<void(FormElement*)>& f) {
|
|
f(this);
|
|
for (auto& mod : m_field_modifications) {
|
|
mod.value->apply(f);
|
|
}
|
|
m_base->apply(f);
|
|
}
|
|
|
|
void ModifiedCopyBitfieldElement::apply_form(const std::function<void(Form*)>& f) {
|
|
for (auto& mod : m_field_modifications) {
|
|
mod.value->apply_form(f);
|
|
}
|
|
m_base->apply_form(f);
|
|
}
|
|
|
|
void ModifiedCopyBitfieldElement::collect_vars(RegAccessSet& vars, bool recursive) const {
|
|
if (recursive) {
|
|
for (auto& mod : m_field_modifications) {
|
|
mod.value->collect_vars(vars, recursive);
|
|
}
|
|
m_base->collect_vars(vars, recursive);
|
|
}
|
|
}
|
|
|
|
void ModifiedCopyBitfieldElement::get_modified_regs(RegSet& regs) const {
|
|
for (auto& mod : m_field_modifications) {
|
|
mod.value->get_modified_regs(regs);
|
|
}
|
|
m_base->get_modified_regs(regs);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// BitfieldReadElement
|
|
////////////////////////////////
|
|
|
|
BitfieldAccessElement::BitfieldAccessElement(Form* base_value, const TypeSpec& ts)
|
|
: m_base(base_value), m_type(ts) {
|
|
m_base->parent_element = this;
|
|
}
|
|
|
|
goos::Object BitfieldAccessElement::to_form_internal(const Env& env) const {
|
|
return pretty_print::build_list("incomplete-bitfield-access", m_base->to_form(env));
|
|
}
|
|
|
|
void BitfieldAccessElement::apply(const std::function<void(FormElement*)>& f) {
|
|
f(this);
|
|
m_base->apply(f);
|
|
}
|
|
|
|
void BitfieldAccessElement::apply_form(const std::function<void(Form*)>& f) {
|
|
m_base->apply_form(f);
|
|
}
|
|
|
|
void BitfieldAccessElement::collect_vars(RegAccessSet& vars, bool recursive) const {
|
|
m_base->collect_vars(vars, recursive);
|
|
}
|
|
|
|
void BitfieldAccessElement::get_modified_regs(RegSet& regs) const {
|
|
m_base->get_modified_regs(regs);
|
|
}
|
|
|
|
const BitField& find_field(const TypeSystem& ts,
|
|
const BitFieldType* type,
|
|
int start_bit,
|
|
int size,
|
|
std::optional<bool> looking_for_unsigned) {
|
|
for (auto& field : type->fields()) {
|
|
if (field.size() == size && field.offset() == start_bit) {
|
|
bool is_int = ts.tc(TypeSpec("integer"), field.type());
|
|
bool is_uint = ts.tc(TypeSpec("uinteger"), field.type());
|
|
// allow sign match, or unsigned+not a number.
|
|
|
|
if (looking_for_unsigned) {
|
|
bool want_unsigned = *looking_for_unsigned;
|
|
if ((is_int && !want_unsigned) || (is_uint && want_unsigned) ||
|
|
(!is_int && !is_uint && want_unsigned)) {
|
|
// matched!
|
|
return field;
|
|
}
|
|
} else {
|
|
return field;
|
|
}
|
|
}
|
|
}
|
|
throw std::runtime_error(
|
|
fmt::format("Unmatched field: start bit {}, size {}, signed? {}, type {}", start_bit, size,
|
|
!looking_for_unsigned, type->get_name()));
|
|
}
|
|
|
|
namespace {
|
|
std::optional<BitField> find_field_from_mask(const TypeSystem& ts,
|
|
const BitFieldType* type,
|
|
uint64_t mask) {
|
|
// try to find a field that is masked by this:
|
|
auto mask_range = get_bit_range(mask);
|
|
if (!mask_range) {
|
|
// it wasn't even a valid mask...
|
|
return {};
|
|
}
|
|
|
|
return find_field(ts, type, mask_range->first(), mask_range->size(), {});
|
|
}
|
|
|
|
Form* strip_int_or_uint_cast(Form* in) {
|
|
auto as_cast = in->try_as_element<CastElement>();
|
|
if (as_cast && (as_cast->type() == TypeSpec("int") || as_cast->type() == TypeSpec("uint"))) {
|
|
return as_cast->source();
|
|
}
|
|
return in;
|
|
}
|
|
|
|
std::optional<BitFieldDef> get_bitfield_initial_set(Form* form,
|
|
const BitFieldType* type,
|
|
const TypeSystem& ts) {
|
|
// (shr (shl arg1 59) 44) for example
|
|
auto matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHR),
|
|
{Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHL),
|
|
{Matcher::any(0), Matcher::any_integer(1)}),
|
|
Matcher::any_integer(2)});
|
|
auto mr = match(matcher, strip_int_or_uint_cast(form));
|
|
if (mr.matched) {
|
|
auto value = mr.maps.forms.at(0);
|
|
int left = mr.maps.ints.at(1);
|
|
int right = mr.maps.ints.at(2);
|
|
int size = 64 - left;
|
|
int offset = left - right;
|
|
auto& f = find_field(ts, type, offset, size, {});
|
|
BitFieldDef def;
|
|
def.value = value;
|
|
def.field_name = f.name();
|
|
def.is_signed = false; // we don't know.
|
|
return def;
|
|
}
|
|
|
|
// also possible to omit the shr if it would be zero:
|
|
auto matcher_no_shr = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHL),
|
|
{Matcher::any(0), Matcher::any_integer(1)});
|
|
auto mr_no_shr = match(matcher_no_shr, strip_int_or_uint_cast(form));
|
|
if (mr_no_shr.matched) {
|
|
auto value = mr_no_shr.maps.forms.at(0);
|
|
int left = mr_no_shr.maps.ints.at(1);
|
|
int right = 0;
|
|
int size = 64 - left;
|
|
int offset = left - right;
|
|
auto& f = find_field(ts, type, offset, size, {});
|
|
BitFieldDef def;
|
|
def.value = value;
|
|
def.field_name = f.name();
|
|
def.is_signed = false; // we don't know.
|
|
return def;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/*!
|
|
* Add a step to the bitfield access. If this completes the access, returns a form representing the
|
|
* access
|
|
*/
|
|
FormElement* BitfieldAccessElement::push_step(const BitfieldManip step,
|
|
const TypeSystem& ts,
|
|
FormPool& pool,
|
|
const Env&) {
|
|
if (m_steps.empty() && step.kind == BitfieldManip::Kind::LEFT_SHIFT) {
|
|
// for left/right shift combo to get a field.
|
|
m_steps.push_back(step);
|
|
return nullptr;
|
|
}
|
|
|
|
if (m_steps.empty() && step.is_right_shift()) {
|
|
// could be a single step right shift to get a field
|
|
bool is_unsigned = step.right_shift_unsigned();
|
|
int shift_size = step.get_shift_start_bit();
|
|
int size = shift_size - step.amount;
|
|
int start_bit = shift_size - size;
|
|
auto type = ts.lookup_type(m_type);
|
|
auto as_bitfield = dynamic_cast<BitFieldType*>(type);
|
|
assert(as_bitfield);
|
|
auto field = find_field(ts, as_bitfield, start_bit, size, is_unsigned);
|
|
auto result =
|
|
pool.alloc_element<DerefElement>(m_base, false, DerefToken::make_field_name(field.name()));
|
|
result->inline_nested();
|
|
return result;
|
|
}
|
|
|
|
if (m_steps.size() == 1 && m_steps.at(0).kind == BitfieldManip::Kind::LEFT_SHIFT) {
|
|
// second op in left/right shift combo
|
|
int end_bit = 64 - m_steps.at(0).amount;
|
|
if (!step.is_right_shift()) {
|
|
throw std::runtime_error("Unexpected step 1");
|
|
}
|
|
bool is_unsigned = step.right_shift_unsigned();
|
|
|
|
int size = 64 - step.amount;
|
|
int start_bit = end_bit - size;
|
|
if (start_bit < 0) {
|
|
throw std::runtime_error("Bad bitfield start bit");
|
|
}
|
|
|
|
auto type = ts.lookup_type(m_type);
|
|
auto as_bitfield = dynamic_cast<BitFieldType*>(type);
|
|
assert(as_bitfield);
|
|
auto field = find_field(ts, as_bitfield, start_bit, size, is_unsigned);
|
|
auto result =
|
|
pool.alloc_element<DerefElement>(m_base, false, DerefToken::make_field_name(field.name()));
|
|
result->inline_nested();
|
|
return result;
|
|
}
|
|
|
|
if (m_steps.empty() && step.kind == BitfieldManip::Kind::LOGAND_WITH_CONSTANT_INT) {
|
|
// and with mask
|
|
m_steps.push_back(step);
|
|
return nullptr;
|
|
}
|
|
|
|
if (m_steps.size() == 1 && m_steps.at(0).kind == BitfieldManip::Kind::LOGAND_WITH_CONSTANT_INT &&
|
|
step.kind == BitfieldManip::Kind::NONZERO_COMPARE) {
|
|
auto type = ts.lookup_type(m_type);
|
|
auto as_bitfield = dynamic_cast<BitFieldType*>(type);
|
|
assert(as_bitfield);
|
|
auto field = find_field_from_mask(ts, as_bitfield, m_steps.at(0).amount);
|
|
if (field) {
|
|
auto get_field = pool.alloc_element<DerefElement>(m_base, false,
|
|
DerefToken::make_field_name(field->name()));
|
|
get_field->inline_nested();
|
|
return pool.alloc_element<GenericElement>(
|
|
GenericOperator::make_compare(IR2_Condition::Kind::NONZERO),
|
|
pool.alloc_single_form(nullptr, get_field));
|
|
}
|
|
}
|
|
|
|
if (m_steps.size() == 1 && m_steps.at(0).kind == BitfieldManip::Kind::LOGAND_WITH_CONSTANT_INT &&
|
|
step.kind == BitfieldManip::Kind::LOGIOR_WITH_CONSTANT_INT) {
|
|
// this is setting a bitfield to a constant.
|
|
// first, let's check that the mask is used properly:
|
|
u64 mask = m_steps.at(0).amount;
|
|
u64 value = step.amount;
|
|
if ((mask & value) != 0) {
|
|
throw std::runtime_error(fmt::format(
|
|
"Got invalid bitmask set: mask was 0x{:x} and value was 0x{:x}", mask, value));
|
|
}
|
|
auto type = ts.lookup_type(m_type);
|
|
auto as_bitfield = dynamic_cast<BitFieldType*>(type);
|
|
assert(as_bitfield);
|
|
// use the mask to figure out the field.
|
|
auto field = find_field_from_mask(ts, as_bitfield, ~mask);
|
|
assert(field);
|
|
bool is_signed =
|
|
ts.tc(TypeSpec("int"), field->type()) && !ts.tc(TypeSpec("uint"), field->type());
|
|
|
|
// use the field to figure out what value is being set.
|
|
u64 set_value;
|
|
if (is_signed) {
|
|
set_value = extract_bitfield<s64>(value, field->offset(), field->size());
|
|
} else {
|
|
set_value = extract_bitfield<u64>(value, field->offset(), field->size());
|
|
}
|
|
|
|
BitFieldDef def;
|
|
def.field_name = field->name();
|
|
def.value = pool.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, SimpleAtom::make_int_constant(set_value));
|
|
return pool.alloc_element<ModifiedCopyBitfieldElement>(m_type, m_base,
|
|
std::vector<BitFieldDef>{def});
|
|
}
|
|
|
|
if (m_steps.size() == 1 && m_steps.at(0).kind == BitfieldManip::Kind::LOGAND_WITH_CONSTANT_INT &&
|
|
step.kind == BitfieldManip::Kind::LOGIOR_WITH_FORM) {
|
|
// this is setting a bitfield to a variable
|
|
u64 mask = m_steps.at(0).amount;
|
|
|
|
auto type = ts.lookup_type(m_type);
|
|
auto as_bitfield = dynamic_cast<BitFieldType*>(type);
|
|
assert(as_bitfield);
|
|
// use the mask to figure out the field.
|
|
auto field = find_field_from_mask(ts, as_bitfield, ~mask);
|
|
assert(field);
|
|
|
|
auto val = get_bitfield_initial_set(step.value, as_bitfield, ts);
|
|
assert(val);
|
|
if (val->field_name != field->name()) {
|
|
throw std::runtime_error("Incompatible bitfield set");
|
|
}
|
|
|
|
return pool.alloc_element<ModifiedCopyBitfieldElement>(m_type, m_base,
|
|
std::vector<BitFieldDef>{*val});
|
|
}
|
|
|
|
lg::error("Invalid state in BitfieldReadElement. Previous steps:");
|
|
for (auto& old_step : m_steps) {
|
|
lg::error(" {}", old_step.print());
|
|
}
|
|
lg::error("Current: {}\n", step.print());
|
|
|
|
throw std::runtime_error("Unknown state in BitfieldReadElement");
|
|
}
|
|
|
|
namespace {
|
|
/*!
|
|
* Nested on the left, will reverse the order.
|
|
*/
|
|
std::vector<Form*> compact_nested_logiors(GenericElement* input, const Env&) {
|
|
std::vector<Form*> result;
|
|
GenericElement* next = input;
|
|
|
|
while (next) {
|
|
assert(next->elts().size() == 2);
|
|
result.push_back(next->elts().at(1));
|
|
auto next_next = next->elts().at(0);
|
|
next = next_next->try_as_element<GenericElement>();
|
|
if (!next || !next->op().is_fixed(FixedOperatorKind::LOGIOR)) {
|
|
result.push_back(next_next);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/*!
|
|
* If this could be an integer constant, figure out what the value is.
|
|
*/
|
|
std::optional<u64> get_goal_integer_constant(Form* in, const Env&) {
|
|
auto as_atom = form_as_atom(in);
|
|
if (as_atom && as_atom->is_int()) {
|
|
return as_atom->get_int();
|
|
}
|
|
|
|
// also (shl <something> 32)
|
|
auto matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHL),
|
|
{Matcher::any(1), Matcher::integer(32)});
|
|
auto mr = match(matcher, in);
|
|
if (mr.matched) {
|
|
auto arg_as_atom = form_as_atom(mr.maps.forms.at(1));
|
|
if (arg_as_atom && arg_as_atom->is_int()) {
|
|
u64 result = arg_as_atom->get_int();
|
|
result <<= 32ull;
|
|
return result;
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
BitFieldDef BitFieldDef::from_constant(const BitFieldConstantDef& constant, FormPool& pool) {
|
|
BitFieldDef bfd;
|
|
bfd.field_name = constant.field_name;
|
|
bfd.is_signed = constant.is_signed;
|
|
if (constant.enum_constant) {
|
|
bfd.value =
|
|
pool.alloc_single_element_form<ConstantTokenElement>(nullptr, *constant.enum_constant);
|
|
} else {
|
|
bfd.value = pool.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, SimpleAtom::make_int_constant(constant.value));
|
|
}
|
|
|
|
return bfd;
|
|
}
|
|
|
|
/*!
|
|
* Cast the given form to a bitfield.
|
|
* If the form could have been a (new 'static 'bitfieldtype ...) it will attempt to generate this.
|
|
*/
|
|
Form* cast_to_bitfield(const BitFieldType* type_info,
|
|
const TypeSpec& typespec,
|
|
FormPool& pool,
|
|
const Env& env,
|
|
Form* in) {
|
|
in = strip_int_or_uint_cast(in);
|
|
// check if it's just a constant:
|
|
auto in_as_atom = form_as_atom(in);
|
|
if (in_as_atom && in_as_atom->is_int()) {
|
|
auto fields = decompile_bitfield_from_int(typespec, env.dts->ts, in_as_atom->get_int());
|
|
return pool.alloc_single_element_form<BitfieldStaticDefElement>(nullptr, typespec, fields,
|
|
pool);
|
|
}
|
|
|
|
auto in_as_generic = strip_int_or_uint_cast(in)->try_as_element<GenericElement>();
|
|
std::vector<Form*> args;
|
|
if (in_as_generic && in_as_generic->op().is_fixed(FixedOperatorKind::LOGIOR)) {
|
|
args = compact_nested_logiors(in_as_generic, env);
|
|
} else {
|
|
args = {strip_int_or_uint_cast(in)};
|
|
}
|
|
|
|
if (!args.empty()) {
|
|
std::vector<BitFieldDef> field_defs;
|
|
|
|
for (auto it = args.begin(); it != args.end(); it++) {
|
|
auto constant = get_goal_integer_constant(*it, env);
|
|
if (constant) {
|
|
auto constant_defs = decompile_bitfield_from_int(typespec, env.dts->ts, *constant);
|
|
for (auto& x : constant_defs) {
|
|
field_defs.push_back(BitFieldDef::from_constant(x, pool));
|
|
}
|
|
|
|
args.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// now variables
|
|
for (auto& arg : args) {
|
|
auto maybe_field =
|
|
get_bitfield_initial_set(strip_int_or_uint_cast(arg), type_info, env.dts->ts);
|
|
if (!maybe_field) {
|
|
// failed, just return cast.
|
|
return pool.alloc_single_element_form<CastElement>(nullptr, typespec, in);
|
|
}
|
|
field_defs.push_back(*maybe_field);
|
|
}
|
|
return pool.alloc_single_element_form<BitfieldStaticDefElement>(nullptr, typespec, field_defs);
|
|
}
|
|
|
|
// all failed, just return whatever.
|
|
return pool.alloc_single_element_form<CastElement>(nullptr, typespec, in);
|
|
}
|
|
|
|
Form* cast_to_bitfield_enum(const EnumType* type_info,
|
|
const TypeSpec& typespec,
|
|
FormPool& pool,
|
|
const Env& env,
|
|
Form* in) {
|
|
assert(type_info->is_bitfield());
|
|
auto integer = get_goal_integer_constant(strip_int_or_uint_cast(in), env);
|
|
if (integer) {
|
|
auto elts =
|
|
decompile_bitfield_enum_from_int(TypeSpec(type_info->get_name()), env.dts->ts, *integer);
|
|
auto oper = GenericOperator::make_function(
|
|
pool.alloc_single_element_form<ConstantTokenElement>(nullptr, type_info->get_name()));
|
|
std::vector<Form*> form_elts;
|
|
for (auto& x : elts) {
|
|
form_elts.push_back(pool.alloc_single_element_form<ConstantTokenElement>(nullptr, x));
|
|
}
|
|
return pool.alloc_single_element_form<GenericElement>(nullptr, oper, form_elts);
|
|
} else {
|
|
// all failed, just return whatever.
|
|
return pool.alloc_single_element_form<CastElement>(nullptr, typespec, in);
|
|
}
|
|
}
|
|
|
|
Form* cast_to_int_enum(const EnumType* type_info,
|
|
const TypeSpec& typespec,
|
|
FormPool& pool,
|
|
const Env& env,
|
|
Form* in) {
|
|
assert(!type_info->is_bitfield());
|
|
auto integer = get_goal_integer_constant(strip_int_or_uint_cast(in), env);
|
|
if (integer) {
|
|
return cast_to_int_enum(type_info, pool, env, *integer);
|
|
} else {
|
|
// all failed, just return whatever.
|
|
return pool.alloc_single_element_form<CastElement>(nullptr, typespec, in);
|
|
}
|
|
}
|
|
|
|
Form* cast_to_int_enum(const EnumType* type_info, FormPool& pool, const Env& env, s64 in) {
|
|
auto entry = decompile_int_enum_from_int(TypeSpec(type_info->get_name()), env.dts->ts, in);
|
|
auto oper = GenericOperator::make_function(
|
|
pool.alloc_single_element_form<ConstantTokenElement>(nullptr, type_info->get_name()));
|
|
return pool.alloc_single_element_form<GenericElement>(
|
|
nullptr, oper, pool.alloc_single_element_form<ConstantTokenElement>(nullptr, entry));
|
|
}
|
|
|
|
} // namespace decompiler
|