aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/hipe/hipe_native_bif.c
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/hipe/hipe_native_bif.c')
-rw-r--r--erts/emulator/hipe/hipe_native_bif.c590
1 files changed, 590 insertions, 0 deletions
diff --git a/erts/emulator/hipe/hipe_native_bif.c b/erts/emulator/hipe/hipe_native_bif.c
new file mode 100644
index 0000000000..f8c2502522
--- /dev/null
+++ b/erts/emulator/hipe/hipe_native_bif.c
@@ -0,0 +1,590 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 2001-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+/* $Id$
+ * hipe_native_bif.c
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sys.h"
+#include "erl_vm.h"
+#include "global.h"
+#include "erl_process.h"
+#include "error.h"
+#include "bif.h"
+#include "erl_bits.h"
+#include "erl_binary.h"
+#include "hipe_mode_switch.h"
+#include "hipe_native_bif.h"
+#include "hipe_arch.h"
+#include "hipe_stack.h"
+
+/*
+ * These are wrappers for BIFs that may trigger a native
+ * stack walk with p->hipe.narity != 0.
+ */
+
+/* for -Wmissing-prototypes :-( */
+extern Eterm hipe_check_process_code_2(Process*, Eterm, Eterm);
+extern Eterm hipe_garbage_collect_1(Process*, Eterm);
+extern Eterm hipe_show_nstack_1(Process*, Eterm);
+
+/* Used when a BIF can trigger a stack walk. */
+static __inline__ void hipe_set_narity(Process *p, unsigned int arity)
+{
+ p->hipe.narity = arity;
+}
+
+Eterm hipe_check_process_code_2(BIF_ALIST_2)
+{
+ Eterm ret;
+
+ hipe_set_narity(BIF_P, 2);
+ ret = check_process_code_2(BIF_P, BIF_ARG_1, BIF_ARG_2);
+ hipe_set_narity(BIF_P, 0);
+ return ret;
+}
+
+Eterm hipe_garbage_collect_1(BIF_ALIST_1)
+{
+ Eterm ret;
+
+ hipe_set_narity(BIF_P, 1);
+ ret = garbage_collect_1(BIF_P, BIF_ARG_1);
+ hipe_set_narity(BIF_P, 0);
+ return ret;
+}
+
+Eterm hipe_show_nstack_1(BIF_ALIST_1)
+{
+ Eterm ret;
+
+ hipe_set_narity(BIF_P, 1);
+ ret = hipe_bifs_show_nstack_1(BIF_P, BIF_ARG_1);
+ hipe_set_narity(BIF_P, 0);
+ return ret;
+}
+
+/*
+ * This is called when inlined heap allocation in native code fails.
+ * The 'need' parameter is the number of heap words needed.
+ * The value is tagged as a fixnum to avoid untagged data on
+ * the x86 stack while the gc is running.
+ */
+void hipe_gc(Process *p, Eterm need)
+{
+ hipe_set_narity(p, 1);
+ p->fcalls -= erts_garbage_collect(p, unsigned_val(need), NULL, 0);
+ hipe_set_narity(p, 0);
+}
+
+/* This is like the OP_setTimeout JAM instruction.
+ * Transformation to the BEAM instruction wait_timeout_fs
+ * has begun.
+ * XXX: BUG: native code should check return status
+ */
+Eterm hipe_set_timeout(Process *p, Eterm timeout_value)
+{
+#if !defined(ARCH_64)
+ Uint time_val;
+#endif
+ /* XXX: This should be converted to follow BEAM conventions,
+ * but that requires some compiler changes.
+ *
+ * In BEAM, set_timeout saves TWO CP values, and suspends.
+ * p->def_arg_reg[0] and p->i are both defined and used.
+ * If a message arrives, BEAM resumes at p->i.
+ * If a timeout fires, BEAM resumes at p->def_arg_reg[0].
+ * (See set_timer() and timeout_proc() in erl_process.c.)
+ *
+ * Here we set p->def_arg_reg[0] to hipe_beam_pc_resume.
+ * Assuming our caller invokes suspend immediately after
+ * our return, then hipe_mode_switch() will also set
+ * p->i to hipe_beam_pc_resume. Thus we'll resume in the same
+ * way regardless of the cause (message or timeout).
+ * hipe_mode_switch() checks for F_TIMO and returns a
+ * flag to native code indicating the cause.
+ */
+
+ /*
+ * def_arg_reg[0] is (re)set unconditionally, in case this is the
+ * 2nd/3rd/... iteration through the receive loop: in order to pass
+ * a boolean flag to native code indicating timeout or new message,
+ * our mode switch has to clobber def_arg_reg[0]. This is ok, but if
+ * we re-suspend (because we ignored a received message) we also have
+ * to reinitialise def_arg_reg[0] with the BEAM resume label.
+ *
+ * XXX: A better solution would be to pass two parameters to
+ * set_timeout: the timeout and the on-timeout resume label.
+ * We could put the resume label in def_arg_reg[1] and resume
+ * at it without having to load a flag in a register and generate
+ * code to test it. Requires a HiPE compiler change though.
+ */
+ p->def_arg_reg[0] = (Eterm) hipe_beam_pc_resume;
+
+ /*
+ * If we have already set the timer, we must NOT set it again. Therefore,
+ * we must test the F_INSLPQUEUE flag as well as the F_TIMO flag.
+ */
+ if (p->flags & (F_INSLPQUEUE | F_TIMO))
+ return NIL; /* caller had better call nbif_suspend ASAP! */
+ if (is_small(timeout_value) && signed_val(timeout_value) >= 0 &&
+#if defined(ARCH_64)
+ (unsigned_val(timeout_value) >> 32) == 0
+#else
+ 1
+#endif
+ ) {
+ set_timer(p, unsigned_val(timeout_value));
+ } else if (timeout_value == am_infinity) {
+ /* p->flags |= F_TIMO; */ /* XXX: nbif_suspend_msg_timeout */
+#if !defined(ARCH_64)
+ } else if (term_to_Uint(timeout_value, &time_val)) {
+ set_timer(p, time_val);
+#endif
+ } else {
+#ifdef ERTS_SMP
+ if (p->hipe_smp.have_receive_locks) {
+ p->hipe_smp.have_receive_locks = 0;
+ erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_MSG_RECEIVE);
+ }
+#endif
+ BIF_ERROR(p, EXC_TIMEOUT_VALUE);
+ }
+ return NIL; /* caller had better call nbif_suspend ASAP! */
+}
+
+/* This is like the remove_message BEAM instruction
+ */
+void hipe_select_msg(Process *p)
+{
+ ErlMessage *msgp;
+
+ msgp = PEEK_MESSAGE(p);
+ UNLINK_MESSAGE(p, msgp); /* decrements global 'erts_proc_tot_mem' variable */
+ JOIN_MESSAGE(p);
+ CANCEL_TIMER(p); /* calls erl_cancel_timer() */
+ free_message(msgp);
+}
+
+void hipe_fclearerror_error(Process *p)
+{
+#if !defined(NO_FPE_SIGNALS)
+ erts_fp_check_init_error(&p->fp_exception);
+#endif
+}
+
+/* Saving a stacktrace from native mode. Right now, we only create a
+ * minimal struct with no fields filled in except freason. The flag
+ * EXF_NATIVE is set, so that build_stacktrace (in beam_emu.c) does not
+ * try to interpret any other field.
+ */
+static void hipe_save_stacktrace(Process* c_p, Eterm args)
+{
+ Eterm *hp;
+ struct StackTrace* s;
+ int sz;
+ int depth = erts_backtrace_depth; /* max depth (never negative) */
+
+ /* Create a container for the exception data. This must be done just
+ as in the save_stacktrace function in beam_emu.c */
+ sz = (offsetof(struct StackTrace, trace) + sizeof(Eterm)*depth
+ + sizeof(Eterm) - 1) / sizeof(Eterm);
+ hp = HAlloc(c_p, 2 + 1 + sz);
+ s = (struct StackTrace *) (hp + 2);
+ c_p->ftrace = CONS(hp, args, make_big((Eterm *) s));
+ s->header = make_pos_bignum_header(sz);
+ s->current = NULL;
+ s->pc = NULL;
+
+ s->depth = hipe_fill_stacktrace(c_p, depth, s->trace);
+
+ /* Must mark this as a native-code exception. */
+ s->freason = NATIVE_EXCEPTION(c_p->freason);
+ return;
+}
+
+/*
+ * hipe_handle_exception() is called from hipe_${ARCH}_glue.S when an
+ * exception has been thrown, to expand the exception value, set the
+ * stack trace, and locate the current handler.
+ */
+void hipe_handle_exception(Process *c_p)
+{
+ Eterm Value = c_p->fvalue;
+ Eterm Args = am_true;
+
+ ASSERT(c_p->freason != TRAP); /* Should have been handled earlier. */
+
+ if (c_p->mbuf) {
+ erts_printf("%s line %u: p==%p, p->mbuf==%p\n", __FUNCTION__, __LINE__, c_p, c_p->mbuf);
+ //erts_garbage_collect(c_p, 0, NULL, 0);
+ }
+
+ /*
+ * Check if we have an arglist for the top level call. If so, this
+ * is encoded in Value, so we have to dig out the real Value as well
+ * as the Arglist.
+ */
+ if (c_p->freason & EXF_ARGLIST) {
+ Eterm *tp;
+ ASSERT(is_tuple(Value));
+ tp = tuple_val(Value);
+ Value = tp[1];
+ Args = tp[2];
+ }
+
+ /* If necessary, build a stacktrace object. */
+ if (c_p->freason & EXF_SAVETRACE)
+ hipe_save_stacktrace(c_p, Args);
+
+ /* Get the fully expanded error term */
+ Value = expand_error_value(c_p, c_p->freason, Value);
+
+ /* Save final error term and stabilize the exception flags so no
+ further expansion is done. */
+ c_p->fvalue = Value;
+ c_p->freason = PRIMARY_EXCEPTION(c_p->freason);
+
+ /* Synthesized to avoid having to generate code for it. */
+ c_p->def_arg_reg[0] = exception_tag[GET_EXC_CLASS(c_p->freason)];
+
+ if (c_p->mbuf) {
+ //erts_printf("%s line %u: p==%p, p->mbuf==%p, p->lastbif==%p\n", __FUNCTION__, __LINE__, c_p, c_p->mbuf, c_p->hipe.lastbif);
+ erts_garbage_collect(c_p, 0, NULL, 0);
+ }
+
+ hipe_find_handler(c_p);
+}
+
+/* This is duplicated from beam_emu.c for now */
+static struct StackTrace *get_trace_from_exc(Eterm exc)
+{
+ if (exc == NIL)
+ return NULL;
+ else
+ return (struct StackTrace *) big_val(CDR(list_val(exc)));
+}
+
+/*
+ * This does what the (misnamed) Beam instruction 'raise_ss' does,
+ * namely, a proper re-throw of an exception that was caught by 'try'.
+ */
+Eterm hipe_rethrow(Process *c_p, Eterm exc, Eterm value)
+{
+ c_p->fvalue = value;
+ if (c_p->freason == EXC_NULL) {
+ /* a safety check for the R10-0 case; should not happen */
+ c_p->ftrace = NIL;
+ BIF_ERROR(c_p, EXC_ERROR);
+ }
+ /* For R10-0 code, 'exc' might be an atom. In that case, just
+ keep the existing c_p->ftrace. */
+ switch (exc) {
+ case am_throw:
+ BIF_ERROR(c_p, (EXC_THROWN & ~EXF_SAVETRACE));
+ break;
+ case am_error:
+ BIF_ERROR(c_p, (EXC_ERROR & ~EXF_SAVETRACE));
+ break;
+ case am_exit:
+ BIF_ERROR(c_p, (EXC_EXIT & ~EXF_SAVETRACE));
+ break;
+ default:
+ {/* R10-1 and later
+ XXX note: should do sanity check on given exception if it can be
+ passed from a user! Currently only expecting generated calls.
+ */
+ struct StackTrace *s;
+ c_p->ftrace = exc;
+ s = get_trace_from_exc(exc);
+ if (s == NULL) {
+ BIF_ERROR(c_p, EXC_ERROR);
+ } else {
+ BIF_ERROR(c_p, PRIMARY_EXCEPTION(s->freason));
+ }
+ }
+ }
+}
+
+/*
+ * Support for compiled binary syntax operations.
+ */
+
+char *hipe_bs_allocate(int len)
+{
+ Binary *bptr;
+
+ bptr = erts_bin_nrml_alloc(len);
+ bptr->flags = 0;
+ bptr->orig_size = len;
+ erts_smp_atomic_init(&bptr->refc, 1);
+ return bptr->orig_bytes;
+}
+
+Binary *hipe_bs_reallocate(Binary* oldbptr, int newsize)
+{
+ Binary *bptr;
+
+ bptr = erts_bin_realloc(oldbptr, newsize);
+ bptr->orig_size = newsize;
+ return bptr;
+}
+
+int hipe_bs_put_big_integer(
+#ifdef ERTS_SMP
+ Process *p,
+#endif
+ Eterm arg, Uint num_bits, byte* base, unsigned offset, unsigned flags)
+{
+ byte *save_bin_buf;
+ Uint save_bin_offset;
+ int res;
+ ERL_BITS_DEFINE_STATEP(p);
+
+ save_bin_buf = erts_current_bin;
+ save_bin_offset = erts_bin_offset;
+ erts_current_bin = base;
+ erts_bin_offset = offset;
+ res = erts_new_bs_put_integer(ERL_BITS_ARGS_3(arg, num_bits, flags));
+ erts_current_bin = save_bin_buf;
+ erts_bin_offset = save_bin_offset;
+ return res;
+}
+
+int hipe_bs_put_small_float(
+ Process *p,
+ Eterm arg, Uint num_bits, byte* base, unsigned offset, unsigned flags)
+{
+ byte *save_bin_buf;
+ Uint save_bin_offset;
+ int res;
+ ERL_BITS_DEFINE_STATEP(p);
+
+ save_bin_buf = erts_current_bin;
+ save_bin_offset = erts_bin_offset;
+ erts_current_bin = base;
+ erts_bin_offset = offset;
+ res = erts_new_bs_put_float(p, arg, num_bits, flags);
+ erts_current_bin = save_bin_buf;
+ erts_bin_offset = save_bin_offset;
+ return res;
+}
+
+void hipe_bs_put_bits(
+ Eterm arg, Uint num_bits, byte* base, unsigned offset, unsigned flags)
+{
+ Uint Bitoffs, Bitsize;
+ byte *Bytep;
+
+ ERTS_GET_BINARY_BYTES(arg, Bytep, Bitoffs, Bitsize);
+ erts_copy_bits(Bytep, Bitoffs, 1, base, offset, 1, num_bits);
+}
+
+Eterm hipe_bs_utf8_size(Eterm arg)
+{
+ /* See beam_emu.c:OpCase(i_bs_utf8_size_sd): error handling
+ is delayed to the subsequent put_utf8 operation. */
+ if (arg < make_small(0x80UL))
+ return make_small(1);
+ else if (arg < make_small(0x800UL))
+ return make_small(2);
+ else if (arg < make_small(0x10000UL))
+ return make_small(3);
+ else
+ return make_small(4);
+}
+
+Eterm hipe_bs_put_utf8(Process *p, Eterm arg, byte *base, unsigned int offset)
+{
+ byte *save_bin_buf;
+ Uint save_bin_offset;
+ int res;
+ unsigned int new_offset;
+ ERL_BITS_DEFINE_STATEP(p);
+
+ save_bin_buf = erts_current_bin;
+ save_bin_offset = erts_bin_offset;
+ erts_current_bin = base;
+ erts_bin_offset = offset;
+ res = erts_bs_put_utf8(ERL_BITS_ARGS_1(arg));
+ new_offset = erts_bin_offset;
+ erts_current_bin = save_bin_buf;
+ erts_bin_offset = save_bin_offset;
+ if (res == 0)
+ BIF_ERROR(p, BADARG);
+ return new_offset;
+}
+
+Eterm hipe_bs_utf16_size(Eterm arg)
+{
+ /* See beam_emu.c:OpCase(i_bs_utf16_size_sd): error handling
+ is delayed to the subsequent put_utf16 operation. */
+ if (arg >= make_small(0x10000UL))
+ return make_small(4);
+ else
+ return make_small(2);
+}
+
+/* This would have used standard_bif_interface_4, which doesn't exist.
+ * Instead we call it via wrappers for the two relevant cases:
+ * (flags & BSF_LITTLE) != 0 and (flags & BSF_LITTLE) == 0.
+ */
+static
+Eterm hipe_bs_put_utf16(Process *p, Eterm arg, byte *base, unsigned int offset, Uint flags)
+{
+ byte *save_bin_buf;
+ Uint save_bin_offset;
+ int res;
+ unsigned int new_offset;
+ ERL_BITS_DEFINE_STATEP(p);
+
+ save_bin_buf = erts_current_bin;
+ save_bin_offset = erts_bin_offset;
+ erts_current_bin = base;
+ erts_bin_offset = offset;
+ res = erts_bs_put_utf16(ERL_BITS_ARGS_2(arg, flags));
+ new_offset = erts_bin_offset;
+ erts_current_bin = save_bin_buf;
+ erts_bin_offset = save_bin_offset;
+ if (res == 0)
+ BIF_ERROR(p, BADARG);
+ return new_offset;
+}
+
+Eterm hipe_bs_put_utf16be(Process *p, Eterm arg, byte *base, unsigned int offset)
+{
+ return hipe_bs_put_utf16(p, arg, base, offset, 0);
+}
+
+Eterm hipe_bs_put_utf16le(Process *p, Eterm arg, byte *base, unsigned int offset)
+{
+ return hipe_bs_put_utf16(p, arg, base, offset, BSF_LITTLE);
+}
+
+static int validate_unicode(Eterm arg)
+{
+ if (is_not_small(arg) ||
+ arg > make_small(0x10FFFFUL) ||
+ (make_small(0xD800UL) <= arg && arg <= make_small(0xDFFFUL)) ||
+ arg == make_small(0xFFFEUL) ||
+ arg == make_small(0xFFFFUL))
+ return 0;
+ return 1;
+}
+
+Eterm hipe_bs_validate_unicode(Process *p, Eterm arg)
+{
+ if (!validate_unicode(arg))
+ BIF_ERROR(p, BADARG);
+ return NIL;
+}
+
+int hipe_bs_validate_unicode_retract(ErlBinMatchBuffer* mb, Eterm arg)
+{
+ if (!validate_unicode(arg)) {
+ mb->offset -= 32;
+ return 0;
+ }
+ return 1;
+}
+
+/* This is like the loop_rec_fr BEAM instruction
+ */
+Eterm hipe_check_get_msg(Process *c_p)
+{
+ Eterm ret;
+ ErlMessage *msgp;
+
+ next_message:
+
+ msgp = PEEK_MESSAGE(c_p);
+
+ if (!msgp) {
+#ifdef ERTS_SMP
+ erts_smp_proc_lock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
+ /* Make sure messages wont pass exit signals... */
+ if (ERTS_PROC_PENDING_EXIT(c_p)) {
+ erts_smp_proc_unlock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
+ return THE_NON_VALUE; /* Will be rescheduled for exit */
+ }
+ ERTS_SMP_MSGQ_MV_INQ2PRIVQ(c_p);
+ msgp = PEEK_MESSAGE(c_p);
+ if (msgp)
+ erts_smp_proc_unlock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
+ else {
+ /* XXX: BEAM doesn't need this */
+ c_p->hipe_smp.have_receive_locks = 1;
+#endif
+ return THE_NON_VALUE;
+#ifdef ERTS_SMP
+ }
+#endif
+ }
+ ErtsMoveMsgAttachmentIntoProc(msgp, c_p, c_p->stop, HEAP_TOP(c_p),
+ c_p->fcalls, (void) 0, (void) 0);
+ ret = ERL_MESSAGE_TERM(msgp);
+ if (is_non_value(ret)) {
+ /*
+ * A corrupt distribution message that we weren't able to decode;
+ * remove it...
+ */
+ ASSERT(!msgp->data.attached);
+ UNLINK_MESSAGE(c_p, msgp);
+ free_message(msgp);
+ goto next_message;
+ }
+ return ret;
+}
+
+/*
+ * SMP-specific stuff
+ */
+#ifdef ERTS_SMP
+
+/*
+ * This is like the timeout BEAM instruction.
+ */
+void hipe_clear_timeout(Process *c_p)
+{
+ /*
+ * A timeout has occurred. Reset the save pointer so that the next
+ * receive statement will examine the first message first.
+ */
+#ifdef ERTS_SMP
+ /* XXX: BEAM has different entries for the locked and unlocked
+ cases. HiPE doesn't, so we must check dynamically. */
+ if (c_p->hipe_smp.have_receive_locks) {
+ c_p->hipe_smp.have_receive_locks = 0;
+ erts_smp_proc_unlock(c_p, ERTS_PROC_LOCKS_MSG_RECEIVE);
+ }
+#endif
+ if (IS_TRACED_FL(c_p, F_TRACE_RECEIVE)) {
+ trace_receive(c_p, am_timeout);
+ }
+ c_p->flags &= ~F_TIMO;
+ JOIN_MESSAGE(c_p);
+}
+
+void hipe_atomic_inc(int *counter)
+{
+ erts_smp_atomic_inc((erts_smp_atomic_t*)counter);
+}
+
+#endif