use super::ir::SsaValueRepr;
use crate::ValueType;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum BuiltinEffect {
Pure,
FallibleHelper,
OwnedMutation,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum InputRepr {
Int,
HeapPtr(HeapInputKind),
Tagged,
Any,
}
#[allow(dead_code)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum HeapInputKind {
String,
Bytes,
Array,
Map,
}
impl HeapInputKind {
pub(crate) fn value_type(self) -> ValueType {
match self {
Self::String => ValueType::String,
Self::Bytes => ValueType::Bytes,
Self::Array => ValueType::Array,
Self::Map => ValueType::Map,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum OutputKind {
Int,
Bool,
Tagged(ValueType),
TypeName,
#[allow(dead_code)] TaggedUnknown,
}
impl OutputKind {
pub(crate) fn repr(self) -> SsaValueRepr {
match self {
Self::Int => SsaValueRepr::I64,
Self::Bool => SsaValueRepr::Bool,
Self::Tagged(_) | Self::TypeName | Self::TaggedUnknown => SsaValueRepr::Tagged,
}
}
}
pub(crate) struct BuiltinSpec {
pub(crate) name: &'static str,
pub(crate) arity: usize,
pub(crate) inputs: &'static [InputRepr],
pub(crate) output: OutputKind,
pub(crate) effect: BuiltinEffect,
pub(crate) needs_failure_exit: bool,
}
impl BuiltinSpec {
pub(crate) fn is_pure(&self) -> bool {
matches!(self.effect, BuiltinEffect::Pure)
}
}
pub(crate) const STRING_LEN_SPEC: BuiltinSpec = BuiltinSpec {
name: "string_len",
arity: 1,
inputs: &[InputRepr::HeapPtr(HeapInputKind::String)],
output: OutputKind::Int,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const REGEX_MATCH_SPEC: BuiltinSpec = BuiltinSpec {
name: "regex_match",
arity: 2,
inputs: &[
InputRepr::HeapPtr(HeapInputKind::String), InputRepr::HeapPtr(HeapInputKind::String), ],
output: OutputKind::Bool,
effect: BuiltinEffect::FallibleHelper,
needs_failure_exit: true,
};
pub(crate) const ARRAY_SET_SPEC: BuiltinSpec = BuiltinSpec {
name: "array_set",
arity: 3,
inputs: &[
InputRepr::Any, InputRepr::Int, InputRepr::Tagged, ],
output: OutputKind::Tagged(ValueType::Array),
effect: BuiltinEffect::OwnedMutation,
needs_failure_exit: true,
};
pub(crate) const VALUE_LEN_SPEC: BuiltinSpec = BuiltinSpec {
name: "value_len",
arity: 1,
inputs: &[InputRepr::Any],
output: OutputKind::Int,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const BYTES_LEN_SPEC: BuiltinSpec = BuiltinSpec {
name: "bytes_len",
arity: 1,
inputs: &[InputRepr::HeapPtr(HeapInputKind::Bytes)],
output: OutputKind::Int,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const ARRAY_LEN_SPEC: BuiltinSpec = BuiltinSpec {
name: "array_len",
arity: 1,
inputs: &[InputRepr::HeapPtr(HeapInputKind::Array)],
output: OutputKind::Int,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const MAP_LEN_SPEC: BuiltinSpec = BuiltinSpec {
name: "map_len",
arity: 1,
inputs: &[InputRepr::HeapPtr(HeapInputKind::Map)],
output: OutputKind::Int,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const TYPE_OF_SPEC: BuiltinSpec = BuiltinSpec {
name: "type_of",
arity: 1,
inputs: &[InputRepr::Any],
output: OutputKind::TypeName,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const STRING_CONTAINS_SPEC: BuiltinSpec = BuiltinSpec {
name: "string_contains",
arity: 2,
inputs: &[
InputRepr::HeapPtr(HeapInputKind::String), InputRepr::HeapPtr(HeapInputKind::String), ],
output: OutputKind::Bool,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const ARRAY_HAS_SPEC: BuiltinSpec = BuiltinSpec {
name: "array_has",
arity: 2,
inputs: &[
InputRepr::Int, InputRepr::HeapPtr(HeapInputKind::Array), ],
output: OutputKind::Bool,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const STRING_SLICE_SPEC: BuiltinSpec = BuiltinSpec {
name: "string_slice",
arity: 3,
inputs: &[
InputRepr::Int, InputRepr::Int, InputRepr::HeapPtr(HeapInputKind::String), ],
output: OutputKind::Tagged(ValueType::String),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const BYTES_SLICE_SPEC: BuiltinSpec = BuiltinSpec {
name: "bytes_slice",
arity: 3,
inputs: &[
InputRepr::Int, InputRepr::Int, InputRepr::HeapPtr(HeapInputKind::Bytes), ],
output: OutputKind::Tagged(ValueType::Bytes),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const STRING_GET_SPEC: BuiltinSpec = BuiltinSpec {
name: "string_get",
arity: 2,
inputs: &[
InputRepr::Int, InputRepr::HeapPtr(HeapInputKind::String), ],
output: OutputKind::Tagged(ValueType::String),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const BYTES_GET_SPEC: BuiltinSpec = BuiltinSpec {
name: "bytes_get",
arity: 2,
inputs: &[
InputRepr::Int, InputRepr::HeapPtr(HeapInputKind::Bytes), ],
output: OutputKind::Int,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const BYTES_HAS_SPEC: BuiltinSpec = BuiltinSpec {
name: "bytes_has",
arity: 2,
inputs: &[
InputRepr::Int, InputRepr::HeapPtr(HeapInputKind::Bytes), ],
output: OutputKind::Bool,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const STRING_REPLACE_LITERAL_SPEC: BuiltinSpec = BuiltinSpec {
name: "string_replace_literal",
arity: 3,
inputs: &[
InputRepr::HeapPtr(HeapInputKind::String), InputRepr::HeapPtr(HeapInputKind::String), InputRepr::HeapPtr(HeapInputKind::String), ],
output: OutputKind::Tagged(ValueType::String),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const STRING_LOWER_ASCII_SPEC: BuiltinSpec = BuiltinSpec {
name: "string_lower_ascii",
arity: 1,
inputs: &[InputRepr::HeapPtr(HeapInputKind::String)],
output: OutputKind::Tagged(ValueType::String),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const STRING_SPLIT_LITERAL_SPEC: BuiltinSpec = BuiltinSpec {
name: "string_split_literal",
arity: 2,
inputs: &[
InputRepr::HeapPtr(HeapInputKind::String), InputRepr::HeapPtr(HeapInputKind::String), ],
output: OutputKind::Tagged(ValueType::Array),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const BYTES_FROM_ARRAY_U8_SPEC: BuiltinSpec = BuiltinSpec {
name: "bytes_from_array_u8",
arity: 1,
inputs: &[InputRepr::HeapPtr(HeapInputKind::Array)],
output: OutputKind::Tagged(ValueType::Bytes),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const BYTES_TO_UTF8_ASCII_SPEC: BuiltinSpec = BuiltinSpec {
name: "bytes_to_utf8_ascii",
arity: 1,
inputs: &[InputRepr::HeapPtr(HeapInputKind::Bytes)],
output: OutputKind::Tagged(ValueType::String),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const BYTES_TO_ARRAY_U8_SPEC: BuiltinSpec = BuiltinSpec {
name: "bytes_to_array_u8",
arity: 1,
inputs: &[InputRepr::HeapPtr(HeapInputKind::Bytes)],
output: OutputKind::Tagged(ValueType::Array),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const TO_STRING_SPEC: BuiltinSpec = BuiltinSpec {
name: "to_string",
arity: 1,
inputs: &[InputRepr::Any],
output: OutputKind::Tagged(ValueType::String),
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const REGEX_REPLACE_SPEC: BuiltinSpec = BuiltinSpec {
name: "regex_replace",
arity: 3,
inputs: &[
InputRepr::HeapPtr(HeapInputKind::String), InputRepr::HeapPtr(HeapInputKind::String), InputRepr::HeapPtr(HeapInputKind::String), ],
output: OutputKind::Tagged(ValueType::String),
effect: BuiltinEffect::FallibleHelper,
needs_failure_exit: true,
};
pub(crate) const ARRAY_GET_SPEC: BuiltinSpec = BuiltinSpec {
name: "array_get",
arity: 2,
inputs: &[
InputRepr::Int, InputRepr::HeapPtr(HeapInputKind::Array), ],
output: OutputKind::TaggedUnknown,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const MAP_GET_SPEC: BuiltinSpec = BuiltinSpec {
name: "map_get",
arity: 2,
inputs: &[
InputRepr::Any, InputRepr::HeapPtr(HeapInputKind::Map), ],
output: OutputKind::TaggedUnknown,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const MAP_HAS_SPEC: BuiltinSpec = BuiltinSpec {
name: "map_has",
arity: 2,
inputs: &[
InputRepr::Any, InputRepr::HeapPtr(HeapInputKind::Map), ],
output: OutputKind::Bool,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const ARRAY_PUSH_SPEC: BuiltinSpec = BuiltinSpec {
name: "array_push",
arity: 2,
inputs: &[
InputRepr::Any, InputRepr::Tagged, ],
output: OutputKind::Tagged(ValueType::Array),
effect: BuiltinEffect::OwnedMutation,
needs_failure_exit: true,
};
pub(crate) const MAP_SET_SPEC: BuiltinSpec = BuiltinSpec {
name: "map_set",
arity: 3,
inputs: &[
InputRepr::Any, InputRepr::Any, InputRepr::Tagged, ],
output: OutputKind::Tagged(ValueType::Map),
effect: BuiltinEffect::OwnedMutation,
needs_failure_exit: true,
};
pub(crate) const MAP_ITER_NEXT_SPEC: BuiltinSpec = BuiltinSpec {
name: "map_iter_next",
arity: 1,
inputs: &[InputRepr::Int], output: OutputKind::Bool,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const MAP_ITER_TAKE_KEY_SPEC: BuiltinSpec = BuiltinSpec {
name: "map_iter_take_key",
arity: 1,
inputs: &[InputRepr::Int], output: OutputKind::TaggedUnknown,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) const MAP_ITER_TAKE_VALUE_SPEC: BuiltinSpec = BuiltinSpec {
name: "map_iter_take_value",
arity: 1,
inputs: &[InputRepr::Int], output: OutputKind::TaggedUnknown,
effect: BuiltinEffect::Pure,
needs_failure_exit: false,
};
pub(crate) fn spec_for(
kind: super::recorder::SpecializedBuiltinKind,
) -> Option<&'static BuiltinSpec> {
match kind {
super::recorder::SpecializedBuiltinKind::StringLen => Some(&STRING_LEN_SPEC),
super::recorder::SpecializedBuiltinKind::RegexMatch => Some(®EX_MATCH_SPEC),
super::recorder::SpecializedBuiltinKind::ArraySet => Some(&ARRAY_SET_SPEC),
super::recorder::SpecializedBuiltinKind::ValueLen => Some(&VALUE_LEN_SPEC),
super::recorder::SpecializedBuiltinKind::BytesLen => Some(&BYTES_LEN_SPEC),
super::recorder::SpecializedBuiltinKind::ArrayLen => Some(&ARRAY_LEN_SPEC),
super::recorder::SpecializedBuiltinKind::MapLen => Some(&MAP_LEN_SPEC),
super::recorder::SpecializedBuiltinKind::TypeOf => Some(&TYPE_OF_SPEC),
super::recorder::SpecializedBuiltinKind::StringContains => Some(&STRING_CONTAINS_SPEC),
super::recorder::SpecializedBuiltinKind::ArrayHas => Some(&ARRAY_HAS_SPEC),
super::recorder::SpecializedBuiltinKind::StringSlice => Some(&STRING_SLICE_SPEC),
super::recorder::SpecializedBuiltinKind::BytesSlice => Some(&BYTES_SLICE_SPEC),
super::recorder::SpecializedBuiltinKind::StringGet => Some(&STRING_GET_SPEC),
super::recorder::SpecializedBuiltinKind::BytesGet => Some(&BYTES_GET_SPEC),
super::recorder::SpecializedBuiltinKind::BytesHas => Some(&BYTES_HAS_SPEC),
super::recorder::SpecializedBuiltinKind::StringReplaceLiteral => {
Some(&STRING_REPLACE_LITERAL_SPEC)
}
super::recorder::SpecializedBuiltinKind::StringLowerAscii => Some(&STRING_LOWER_ASCII_SPEC),
super::recorder::SpecializedBuiltinKind::StringSplitLiteral => {
Some(&STRING_SPLIT_LITERAL_SPEC)
}
super::recorder::SpecializedBuiltinKind::BytesFromArrayU8 => {
Some(&BYTES_FROM_ARRAY_U8_SPEC)
}
super::recorder::SpecializedBuiltinKind::BytesToUtf8Ascii => {
Some(&BYTES_TO_UTF8_ASCII_SPEC)
}
super::recorder::SpecializedBuiltinKind::BytesToArrayU8 => Some(&BYTES_TO_ARRAY_U8_SPEC),
super::recorder::SpecializedBuiltinKind::ToString => Some(&TO_STRING_SPEC),
super::recorder::SpecializedBuiltinKind::RegexReplace => Some(®EX_REPLACE_SPEC),
super::recorder::SpecializedBuiltinKind::ArrayGet => Some(&ARRAY_GET_SPEC),
super::recorder::SpecializedBuiltinKind::MapGet => Some(&MAP_GET_SPEC),
super::recorder::SpecializedBuiltinKind::MapHas => Some(&MAP_HAS_SPEC),
super::recorder::SpecializedBuiltinKind::ArrayPush => Some(&ARRAY_PUSH_SPEC),
super::recorder::SpecializedBuiltinKind::MapSet => Some(&MAP_SET_SPEC),
super::recorder::SpecializedBuiltinKind::MapIterNext => Some(&MAP_ITER_NEXT_SPEC),
super::recorder::SpecializedBuiltinKind::MapIterTakeKey => Some(&MAP_ITER_TAKE_KEY_SPEC),
super::recorder::SpecializedBuiltinKind::MapIterTakeValue => {
Some(&MAP_ITER_TAKE_VALUE_SPEC)
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_SPECS: &[&BuiltinSpec] = &[
&STRING_LEN_SPEC,
®EX_MATCH_SPEC,
&ARRAY_SET_SPEC,
&VALUE_LEN_SPEC,
&BYTES_LEN_SPEC,
&ARRAY_LEN_SPEC,
&MAP_LEN_SPEC,
&TYPE_OF_SPEC,
&STRING_CONTAINS_SPEC,
&ARRAY_HAS_SPEC,
];
#[test]
fn all_specs_have_consistent_arity_and_inputs() {
for spec in ALL_SPECS {
assert_eq!(
spec.arity,
spec.inputs.len(),
"{}: arity must match inputs",
spec.name
);
}
}
#[test]
fn effect_classification_is_explicit() {
const {
assert!(matches!(STRING_LEN_SPEC.effect, BuiltinEffect::Pure));
assert!(!STRING_LEN_SPEC.needs_failure_exit);
assert!(matches!(
REGEX_MATCH_SPEC.effect,
BuiltinEffect::FallibleHelper
));
assert!(REGEX_MATCH_SPEC.needs_failure_exit);
assert!(matches!(
ARRAY_SET_SPEC.effect,
BuiltinEffect::OwnedMutation
));
assert!(ARRAY_SET_SPEC.needs_failure_exit);
}
for spec in ALL_SPECS {
if spec.needs_failure_exit {
assert!(
!matches!(spec.effect, BuiltinEffect::Pure),
"{}: pure builtins must not need failure exit",
spec.name
);
}
}
}
}