summaryrefslogtreecommitdiff
path: root/erts/emulator/beam/jit/beam_jit_common.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/beam/jit/beam_jit_common.hpp')
-rw-r--r--erts/emulator/beam/jit/beam_jit_common.hpp386
1 files changed, 385 insertions, 1 deletions
diff --git a/erts/emulator/beam/jit/beam_jit_common.hpp b/erts/emulator/beam/jit/beam_jit_common.hpp
index 41cf957daa..c7b9f0ade0 100644
--- a/erts/emulator/beam/jit/beam_jit_common.hpp
+++ b/erts/emulator/beam/jit/beam_jit_common.hpp
@@ -1,7 +1,7 @@
/*
* %CopyrightBegin%
*
- * Copyright Ericsson AB 2021-2022. All Rights Reserved.
+ * Copyright Ericsson AB 2021-2023. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,10 @@
#ifndef __BEAM_JIT_COMMON_HPP__
#define __BEAM_JIT_COMMON_HPP__
+#ifndef ASMJIT_ASMJIT_H_INCLUDED
+# include <asmjit/asmjit.hpp>
+#endif
+
#include <string>
#include <vector>
#include <unordered_map>
@@ -36,10 +40,390 @@ extern "C"
#include "bif.h"
#include "erl_vm.h"
#include "global.h"
+#include "big.h"
#include "beam_file.h"
+#include "beam_types.h"
}
+/* On Windows, the min and max macros may be defined. */
+#undef min
+#undef max
+
#include "beam_jit_args.hpp"
+#include "beam_jit_types.hpp"
+
+using namespace asmjit;
+
+struct AsmRange {
+ ErtsCodePtr start;
+ ErtsCodePtr stop;
+ const std::string name;
+
+ struct LineData {
+ ErtsCodePtr start;
+ const std::string file;
+ unsigned line;
+ };
+
+ const std::vector<LineData> lines;
+};
+
+/* This is a partial class for `BeamAssembler`, containing various fields and
+ * helper functions that are common to all architectures so that we won't have
+ * to redefine them all the time. */
+class BeamAssemblerCommon : public ErrorHandler {
+ BaseAssembler &assembler;
+
+protected:
+ CodeHolder code;
+ FileLogger logger;
+ Section *rodata;
+
+ static bool hasCpuFeature(uint32_t featureId);
+
+ BeamAssemblerCommon(BaseAssembler &assembler);
+ ~BeamAssemblerCommon();
+
+ void codegen(JitAllocator *allocator,
+ const void **executable_ptr,
+ void **writable_ptr);
+
+ void comment(const char *format) {
+ if (logger.file()) {
+ assembler.commentf("# %s", format);
+ }
+ }
+
+ template<typename... Ts>
+ void comment(const char *format, Ts... args) {
+ if (logger.file()) {
+ char buff[1024];
+ erts_snprintf(buff, sizeof(buff), format, args...);
+ assembler.commentf("# %s", buff);
+ }
+ }
+
+ void *getCode(Label label);
+ byte *getCode(char *labelName);
+
+ void embed(void *data, uint32_t size) {
+ assembler.embed((char *)data, size);
+ }
+
+ void handleError(Error err, const char *message, BaseEmitter *origin);
+
+ void setLogger(const std::string &log);
+ void setLogger(FILE *log);
+
+public:
+ void embed_rodata(const char *labelName, const char *buff, size_t size);
+ void embed_bss(const char *labelName, size_t size);
+ void embed_zeros(size_t size);
+
+ void *getBaseAddress();
+ size_t getOffset();
+};
+
+struct BeamModuleAssemblerCommon {
+ typedef unsigned BeamLabel;
+
+ /* The BEAM file we've been loaded from, if any. */
+ const BeamFile *beam;
+ Eterm mod;
+
+ /* Map of label number to asmjit Label */
+ typedef std::unordered_map<BeamLabel, const Label> LabelMap;
+ LabelMap rawLabels;
+
+ struct patch {
+ Label where;
+ size_t ptr_offs;
+ size_t val_offs;
+ };
+
+ struct patch_catch {
+ struct patch patch;
+ Label handler;
+ };
+ std::vector<struct patch_catch> catches;
+
+ /* Map of import entry to patch labels and mfa */
+ struct patch_import {
+ std::vector<struct patch> patches;
+ ErtsCodeMFA mfa;
+ };
+ typedef std::unordered_map<unsigned, struct patch_import> ImportMap;
+ ImportMap imports;
+
+ /* Map of fun entry to trampoline labels and patches */
+ struct patch_lambda {
+ std::vector<struct patch> patches;
+ Label trampoline;
+ };
+ typedef std::unordered_map<unsigned, struct patch_lambda> LambdaMap;
+ LambdaMap lambdas;
+
+ /* Map of literals to patch labels */
+ struct patch_literal {
+ std::vector<struct patch> patches;
+ };
+ typedef std::unordered_map<unsigned, struct patch_literal> LiteralMap;
+ LiteralMap literals;
+
+ /* All string patches */
+ std::vector<struct patch> strings;
+
+ /* All functions that have been seen so far */
+ std::vector<BeamLabel> functions;
+
+ /* Used by emit to populate the labelToMFA map */
+ Label current_label;
+
+ /* The offset of our BeamCodeHeader, if any. */
+ Label code_header;
+
+ /* The module's on_load function, if any. */
+ Label on_load;
+
+ /* The end of the last function. Note that the dispatch table, constants,
+ * and veneers may follow. */
+ Label code_end;
+
+ BeamModuleAssemblerCommon(const BeamFile *beam_, Eterm mod_)
+ : beam(beam_), mod(mod_) {
+ }
+
+ /* Helpers */
+ const auto &getTypeEntry(const ArgSource &arg) const {
+ auto typeIndex =
+ arg.isRegister() ? arg.as<ArgRegister>().typeIndex() : 0;
+ ASSERT(typeIndex < beam->types.count);
+ return static_cast<BeamArgType &>(beam->types.entries[typeIndex]);
+ }
+
+ auto getTypeUnion(const ArgSource &arg) const {
+ if (arg.isRegister()) {
+ return getTypeEntry(arg).type();
+ }
+
+ Eterm constant =
+ arg.isImmed()
+ ? arg.as<ArgImmed>().get()
+ : beamfile_get_literal(beam,
+ arg.as<ArgLiteral>().get());
+
+ switch (tag_val_def(constant)) {
+ case ATOM_DEF:
+ return BeamTypeId::Atom;
+ case BINARY_DEF:
+ return BeamTypeId::Bitstring;
+ case FLOAT_DEF:
+ return BeamTypeId::Float;
+ case FUN_DEF:
+ return BeamTypeId::Fun;
+ case BIG_DEF:
+ case SMALL_DEF:
+ return BeamTypeId::Integer;
+ case LIST_DEF:
+ return BeamTypeId::Cons;
+ case MAP_DEF:
+ return BeamTypeId::Map;
+ case NIL_DEF:
+ return BeamTypeId::Nil;
+ case EXTERNAL_PID_DEF:
+ case PID_DEF:
+ return BeamTypeId::Pid;
+ case EXTERNAL_PORT_DEF:
+ case PORT_DEF:
+ return BeamTypeId::Port;
+ case EXTERNAL_REF_DEF:
+ case REF_DEF:
+ return BeamTypeId::Reference;
+ case TUPLE_DEF:
+ return BeamTypeId::Tuple;
+ default:
+ ERTS_ASSERT(!"tag_val_def error");
+ }
+ }
+
+ auto getClampedRange(const ArgSource &arg) const {
+ if (arg.isSmall()) {
+ Sint value = arg.as<ArgSmall>().getSigned();
+ return std::make_pair(value, value);
+ } else {
+ const auto &entry = getTypeEntry(arg);
+
+ auto min = entry.hasLowerBound() ? entry.min() : MIN_SMALL;
+ auto max = entry.hasUpperBound() ? entry.max() : MAX_SMALL;
+
+ return std::make_pair(min, max);
+ }
+ }
+
+ bool always_small(const ArgSource &arg) const {
+ if (arg.isSmall()) {
+ return true;
+ } else if (!exact_type<BeamTypeId::Integer>(arg)) {
+ return false;
+ }
+
+ const auto &entry = getTypeEntry(arg);
+
+ if (entry.hasLowerBound() && entry.hasUpperBound()) {
+ return IS_SSMALL(entry.min()) && IS_SSMALL(entry.max());
+ }
+
+ return false;
+ }
+
+ bool always_immediate(const ArgSource &arg) const {
+ return always_one_of<BeamTypeId::AlwaysImmediate>(arg) ||
+ always_small(arg);
+ }
+
+ bool always_same_types(const ArgSource &lhs, const ArgSource &rhs) const {
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto lhs_types = static_cast<integral>(getTypeUnion(lhs));
+ auto rhs_types = static_cast<integral>(getTypeUnion(rhs));
+
+ /* We can only be certain that the types are the same when there's
+ * one possible type. For example, if one is a number and the other
+ * is an integer, they could differ if the former is a float. */
+ if ((lhs_types & (lhs_types - 1)) == 0) {
+ return lhs_types == rhs_types;
+ }
+
+ return false;
+ }
+
+ template<BeamTypeId... Types>
+ bool never_one_of(const ArgSource &arg) const {
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto types = static_cast<integral>(BeamTypeIdUnion<Types...>::value());
+ auto type_union = static_cast<integral>(getTypeUnion(arg));
+ return static_cast<BeamTypeId>(type_union & types) == BeamTypeId::None;
+ }
+
+ template<BeamTypeId... Types>
+ bool maybe_one_of(const ArgSource &arg) const {
+ return !never_one_of<Types...>(arg);
+ }
+
+ template<BeamTypeId... Types>
+ bool always_one_of(const ArgSource &arg) const {
+ /* Providing a single type to this function is not an error per se, but
+ * `exact_type` provides a bit more error checking for that use-case,
+ * so we want to encourage its use. */
+ static_assert(!BeamTypeIdUnion<Types...>::is_single_typed(),
+ "always_one_of expects a union of several primitive "
+ "types, use exact_type instead");
+
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto types = static_cast<integral>(BeamTypeIdUnion<Types...>::value());
+ auto type_union = static_cast<integral>(getTypeUnion(arg));
+ return type_union == (type_union & types);
+ }
+
+ template<BeamTypeId Type>
+ bool exact_type(const ArgSource &arg) const {
+ /* Rejects `exact_type<BeamTypeId::List>(...)` and similar, as it's
+ * almost always an error to exactly match a union of several types.
+ *
+ * On the off chance that you _do_ need to match a union exactly, use
+ * `masked_types<T>(arg) == T` instead. */
+ static_assert(BeamTypeIdUnion<Type>::is_single_typed(),
+ "exact_type expects exactly one primitive type, use "
+ "always_one_of instead");
+
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto type_union = static_cast<integral>(getTypeUnion(arg));
+ return type_union == (type_union & static_cast<integral>(Type));
+ }
+
+ template<BeamTypeId Mask>
+ BeamTypeId masked_types(const ArgSource &arg) const {
+ static_assert((Mask != BeamTypeId::AlwaysBoxed &&
+ Mask != BeamTypeId::AlwaysImmediate),
+ "using masked_types with AlwaysBoxed or AlwaysImmediate "
+ "is almost always an error, use exact_type, "
+ "maybe_one_of, or never_one_of instead");
+ static_assert((Mask == BeamTypeId::MaybeBoxed ||
+ Mask == BeamTypeId::MaybeImmediate ||
+ Mask == BeamTypeId::AlwaysBoxed ||
+ Mask == BeamTypeId::AlwaysImmediate),
+ "masked_types expects a mask type like MaybeBoxed or "
+ "MaybeImmediate, use exact_type, maybe_one_of, or"
+ "never_one_of instead");
+
+ using integral = std::underlying_type_t<BeamTypeId>;
+ auto mask = static_cast<integral>(Mask);
+ auto type_union = static_cast<integral>(getTypeUnion(arg));
+ return static_cast<BeamTypeId>(type_union & mask);
+ }
+
+ bool is_sum_small_if_args_are_small(const ArgSource &LHS,
+ const ArgSource &RHS) const {
+ Sint min, max;
+ auto [min1, max1] = getClampedRange(LHS);
+ auto [min2, max2] = getClampedRange(RHS);
+ min = min1 + min2;
+ max = max1 + max2;
+ return IS_SSMALL(min) && IS_SSMALL(max);
+ }
+
+ bool is_diff_small_if_args_are_small(const ArgSource &LHS,
+ const ArgSource &RHS) const {
+ Sint min, max;
+ auto [min1, max1] = getClampedRange(LHS);
+ auto [min2, max2] = getClampedRange(RHS);
+ min = min1 - max2;
+ max = max1 - min2;
+ return IS_SSMALL(min) && IS_SSMALL(max);
+ }
+
+ bool is_product_small_if_args_are_small(const ArgSource &LHS,
+ const ArgSource &RHS) const {
+ auto [min1, max1] = getClampedRange(LHS);
+ auto [min2, max2] = getClampedRange(RHS);
+ auto mag1 = std::max(std::abs(min1), std::abs(max1));
+ auto mag2 = std::max(std::abs(min2), std::abs(max2));
+
+ /*
+ * mag1 * mag2 <= MAX_SMALL
+ * mag1 <= MAX_SMALL / mag2 (when mag2 != 0)
+ */
+ ERTS_CT_ASSERT(MAX_SMALL < -MIN_SMALL);
+ return mag2 == 0 || mag1 <= MAX_SMALL / mag2;
+ }
+
+ bool is_bsl_small(const ArgSource &LHS, const ArgSource &RHS) const {
+ if (!(always_small(LHS) && always_small(RHS))) {
+ return false;
+ } else {
+ auto [min1, max1] = getClampedRange(LHS);
+ auto [min2, max2] = getClampedRange(RHS);
+
+ if (min1 < 0 || max1 == 0 || min2 < 0) {
+ return false;
+ }
+
+ return max2 < asmjit::Support::clz(max1) - _TAG_IMMED1_SIZE;
+ }
+ }
+
+ int getSizeUnit(const ArgSource &arg) const {
+ auto entry = getTypeEntry(arg);
+ return entry.hasUnit() ? entry.unit() : 1;
+ }
+
+ bool hasLowerBound(const ArgSource &arg) const {
+ return getTypeEntry(arg).hasLowerBound();
+ }
+
+ bool hasUpperBound(const ArgSource &arg) const {
+ return getTypeEntry(arg).hasUpperBound();
+ }
+};
/* This is a view into a contiguous container (like an array or `std::vector`),
* letting us reuse the existing argument array in `beamasm_emit` while keeping