Skip to content
This repository has been archived by the owner on Jun 10, 2024. It is now read-only.

Commit

Permalink
feat: implement a number of tuple-oriented bifs
Browse files Browse the repository at this point in the history
* is_tuple/1
* tuple_size/1
* tuple_to_list/1
* make_tuple/2
* make_tuple/3
* list_to_tuple/1
* element/2
* setelement/3
  • Loading branch information
bitwalker committed Mar 17, 2023
1 parent 7cfdab8 commit 8e89bc7
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 37 deletions.
1 change: 1 addition & 0 deletions library/rt/src/bifs/erlang/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod binaries;
pub mod tuples;
193 changes: 193 additions & 0 deletions library/rt/src/bifs/erlang/tuples.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
use firefly_alloc::heap::Heap;

use smallvec::SmallVec;

use crate::function::ErlangResult;
use crate::gc::{garbage_collect, RootSet};
use crate::process::ProcessLock;
use crate::term::*;

#[export_name = "erlang:is_tuple/1"]
pub extern "C" fn is_tuple(_process: &mut ProcessLock, tuple: OpaqueTerm) -> ErlangResult {
ErlangResult::Ok(tuple.is_tuple().into())
}

#[export_name = "erlang:tuple_size/1"]
pub extern "C-unwind" fn tuple_size1(process: &mut ProcessLock, tuple: OpaqueTerm) -> ErlangResult {
match tuple.tuple_size() {
Ok(arity) => ErlangResult::Ok(Term::Int(arity as i64).into()),
Err(_) => badarg!(process, tuple),
}
}

#[export_name = "erlang:element/2"]
pub extern "C" fn element2(
process: &mut ProcessLock,
index: OpaqueTerm,
tuple_term: OpaqueTerm,
) -> ErlangResult {
let Ok(index) = OneBasedIndex::try_from(index) else { badarg!(process, index); };
let Term::Tuple(tuple) = tuple_term.into() else { badarg!(process, tuple_term); };
match tuple.get_element(index) {
Some(element) => ErlangResult::Ok(element),
None => badarg!(process, tuple_term),
}
}

#[export_name = "erlang:setelement/3"]
pub extern "C" fn setelement3(
process: &mut ProcessLock,
index_term: OpaqueTerm,
tuple_term: OpaqueTerm,
value: OpaqueTerm,
) -> ErlangResult {
let Ok(index) = OneBasedIndex::try_from(index_term) else { badarg!(process, index_term); };
let Term::Tuple(tuple) = tuple_term.into() else { badarg!(process, tuple_term); };
if index > tuple.len() {
badarg!(process, index_term);
}

match tuple.set_element(index, value, process) {
Ok(new_tuple) => ErlangResult::Ok(new_tuple.into()),
Err(_) => {
let mut roots = RootSet::default();
let mut tuple = Term::Tuple(tuple);
roots += &mut tuple as *mut Term;
assert!(garbage_collect(process, roots).is_ok());
let Term::Tuple(tuple) = tuple else { unreachable!() };
ErlangResult::Ok(tuple.set_element(index, value, process).unwrap().into())
}
}
}

#[export_name = "erlang:tuple_to_list/1"]
pub extern "C-unwind" fn tuple_to_list1(
process: &mut ProcessLock,
mut tuple: OpaqueTerm,
) -> ErlangResult {
let Ok(arity) = tuple.tuple_size() else { badarg!(process, tuple); };

let mut layout = LayoutBuilder::new();
layout.build_list(arity as usize);
let needed = layout.finish().size();
if needed > process.heap_available() {
let mut roots = RootSet::default();
roots += &mut tuple as *mut OpaqueTerm;
assert!(garbage_collect(process, roots).is_ok());
}

let Term::Tuple(tuple) = tuple.into() else { unreachable!() };
let mut builder = ListBuilder::new(process);
for term in tuple.as_slice() {
unsafe {
builder.push_unsafe(*term).unwrap();
}
}
ErlangResult::Ok(builder.finish().map(Term::Cons).unwrap_or(Term::Nil).into())
}

#[export_name = "erlang:make_tuple/2"]
pub extern "C-unwind" fn make_tuple2(
process: &mut ProcessLock,
arity: OpaqueTerm,
initial_value: OpaqueTerm,
) -> ErlangResult {
make_tuple3(process, arity, initial_value, OpaqueTerm::NIL)
}

#[export_name = "erlang:make_tuple/3"]
pub extern "C-unwind" fn make_tuple3(
process: &mut ProcessLock,
arity: OpaqueTerm,
mut default_value: OpaqueTerm,
mut init_list: OpaqueTerm,
) -> ErlangResult {
let arity = usize_or_badarg!(process, arity.into());
if !init_list.is_list() {
badarg!(process, init_list);
}

let mut layout = LayoutBuilder::new();
layout.build_tuple(arity);
let needed = layout.finish().size();
if needed > process.heap_available() {
let mut roots = RootSet::default();
roots += &mut default_value as *mut OpaqueTerm;
roots += &mut init_list as *mut OpaqueTerm;
assert!(garbage_collect(process, roots).is_ok());
}

let heap_top = process.heap_top();
let mut tuple = Tuple::new_in(arity, process).unwrap();
tuple.as_mut_slice().fill(default_value);
if let Term::Cons(init) = init_list.into() {
for init_term in init.iter() {
match init_term {
Ok(Term::Tuple(init_tuple)) if init_tuple.len() == 2 => {
match OneBasedIndex::try_from(init_tuple[0]) {
Ok(index) => {
tuple[index] = init_tuple[1];
}
Err(_) => {
unsafe {
process.reset_heap_top(heap_top);
}
badarg!(process, init_list);
}
}
}
_ => {
unsafe {
process.reset_heap_top(heap_top);
}
badarg!(process, init_list);
}
}
}
}

ErlangResult::Ok(tuple.into())
}

#[export_name = "erlang:list_to_tuple/1"]
pub extern "C-unwind" fn list_to_tuple1(
process: &mut ProcessLock,
list: OpaqueTerm,
) -> ErlangResult {
match list.into() {
Term::Nil => {
let mut layout = LayoutBuilder::new();
layout.build_tuple(0);
let needed = layout.finish().size();
if needed > process.heap_available() {
assert!(garbage_collect(process, Default::default()).is_ok());
}
let tuple = Tuple::new_in(0, process).unwrap();
ErlangResult::Ok(tuple.into())
}
Term::Cons(cons) => {
let mut items = SmallVec::<[OpaqueTerm; 8]>::default();
for maybe_improper in cons.iter_raw() {
match maybe_improper {
Ok(term) => {
items.push(term);
}
Err(_) => badarg!(process, list),
}
}
let mut layout = LayoutBuilder::new();
layout.build_tuple(items.len());
let needed = layout.finish().size();
if needed > process.heap_available() {
let mut roots = RootSet::default();
for item in items.iter_mut() {
roots += item as *mut OpaqueTerm;
}
assert!(garbage_collect(process, roots).is_ok());
}
let tuple = Tuple::from_slice(items.as_slice(), process).unwrap();
ErlangResult::Ok(tuple.into())
}
_ => badarg!(process, list),
}
}
84 changes: 47 additions & 37 deletions library/rt/src/term/index.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
use core::cmp;
use core::fmt;
use core::ops;
use core::slice;

use anyhow::anyhow;

use firefly_number::ToPrimitive;

use super::{BigInt, OpaqueTerm, Term, Tuple};
use super::{OpaqueTerm, Term, Tuple};

/// A marker trait for index types
pub trait TupleIndex: Into<usize> {}
/// A marker trait for internal index types to help in specialization
pub trait NonPrimitiveIndex: Sized {}

macro_rules! bad_index {
() => {
anyhow!("invalid index: bad argument")
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct InvalidTupleIndex;
impl fmt::Display for InvalidTupleIndex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid tuple index")
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidTupleIndex {}

/// Represents indices which start at 1 and progress upwards
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct OneBasedIndex(usize);
impl OneBasedIndex {
#[inline]
pub fn new(i: usize) -> anyhow::Result<Self> {
pub fn new(i: usize) -> Result<Self, InvalidTupleIndex> {
if i > 0 {
Ok(Self(i))
} else {
Err(bad_index!())
Err(InvalidTupleIndex)
}
}
}
Expand All @@ -40,41 +41,43 @@ impl Default for OneBasedIndex {
Self(1)
}
}
impl TryFrom<&BigInt> for OneBasedIndex {
type Error = anyhow::Error;
impl TryFrom<u32> for OneBasedIndex {
type Error = InvalidTupleIndex;

fn try_from(n: &BigInt) -> Result<Self, Self::Error> {
Self::new(n.try_into().map_err(|_| bad_index!())?)
#[inline]
fn try_from(n: u32) -> Result<Self, Self::Error> {
Self::new(n as usize)
}
}
impl TryFrom<i64> for OneBasedIndex {
type Error = anyhow::Error;
type Error = InvalidTupleIndex;

fn try_from(n: i64) -> Result<Self, Self::Error> {
Self::new(n.try_into().map_err(|_| bad_index!())?)
Self::new(n.try_into().map_err(|_| InvalidTupleIndex)?)
}
}
impl TryFrom<OpaqueTerm> for OneBasedIndex {
type Error = anyhow::Error;
type Error = InvalidTupleIndex;

fn try_from(term: OpaqueTerm) -> Result<Self, Self::Error> {
let term: Term = term.into();
term.try_into()
if !term.is_integer() {
return Err(InvalidTupleIndex);
}
unsafe { term.as_integer().try_into() }
}
}
impl TryFrom<Term> for OneBasedIndex {
type Error = anyhow::Error;
type Error = InvalidTupleIndex;

fn try_from(term: Term) -> Result<Self, Self::Error> {
match term {
Term::Int(i) => i.try_into(),
Term::BigInt(i) => i.as_ref().try_into(),
_ => Err(bad_index!()),
Term::Int(i) if i > 0 => Ok(Self(i.try_into().map_err(|_| InvalidTupleIndex)?)),
_ => Err(InvalidTupleIndex),
}
}
}
impl Into<usize> for OneBasedIndex {
#[inline]
#[inline(always)]
fn into(self) -> usize {
self.0 - 1
}
Expand Down Expand Up @@ -139,29 +142,36 @@ impl Default for ZeroBasedIndex {
Self(0)
}
}
impl From<u32> for ZeroBasedIndex {
#[inline(always)]
fn from(n: u32) -> Self {
Self(n as usize)
}
}
impl TryFrom<i64> for ZeroBasedIndex {
type Error = anyhow::Error;
type Error = InvalidTupleIndex;

fn try_from(n: i64) -> Result<Self, Self::Error> {
Ok(Self::new(n.try_into().map_err(|_| bad_index!())?))
Ok(Self(n.try_into().map_err(|_| InvalidTupleIndex)?))
}
}
impl TryFrom<OpaqueTerm> for ZeroBasedIndex {
type Error = anyhow::Error;
type Error = InvalidTupleIndex;

fn try_from(term: OpaqueTerm) -> Result<Self, Self::Error> {
let term: Term = term.into();
term.try_into()
if !term.is_integer() {
return Err(InvalidTupleIndex);
}
unsafe { term.as_integer().try_into() }
}
}
impl TryFrom<Term> for ZeroBasedIndex {
type Error = anyhow::Error;
type Error = InvalidTupleIndex;

fn try_from(term: Term) -> Result<Self, Self::Error> {
match term {
Term::Int(i) => i.try_into().map_err(|_| bad_index!()),
Term::BigInt(i) => i.to_i64().ok_or_else(|| bad_index!())?.try_into(),
_ => Err(bad_index!()),
Term::Int(i) => i.try_into().map_err(|_| InvalidTupleIndex),
_ => Err(InvalidTupleIndex),
}
}
}
Expand All @@ -172,13 +182,13 @@ impl From<OneBasedIndex> for ZeroBasedIndex {
}
}
impl From<usize> for ZeroBasedIndex {
#[inline]
#[inline(always)]
fn from(n: usize) -> Self {
Self::new(n)
Self(n)
}
}
impl Into<usize> for ZeroBasedIndex {
#[inline]
#[inline(always)]
fn into(self) -> usize {
self.0
}
Expand Down

0 comments on commit 8e89bc7

Please sign in to comment.