slovo/compiler/src/llvm.rs

2800 lines
103 KiB
Rust

use std::collections::{HashMap, HashSet};
use crate::{
ast::{BinaryOp, MatchPatternKind},
check::{CheckedFunction, CheckedProgram, TExpr, TExprKind, TMatchArm},
diag::Diagnostic,
std_runtime,
types::Type,
};
pub fn emit(_file: &str, program: &CheckedProgram) -> Result<String, Vec<Diagnostic>> {
let mut out = String::new();
let layouts = struct_layouts(program);
let enum_layouts = enum_layouts(program);
let string_globals = StringGlobals::collect(program);
let needs_process_runtime = needs_process_runtime(program);
out.push_str("; Module generated by glagol\n");
out.push_str(&format!("; Slovo module: {}\n\n", program.module));
out.push_str("declare void @print_i32(i32)\n\n");
out.push_str("declare void @print_i64(i64)\n\n");
out.push_str("declare void @print_u32(i32)\n\n");
out.push_str("declare void @print_u64(i64)\n\n");
out.push_str("declare void @print_f64(double)\n\n");
out.push_str("declare void @print_string(ptr)\n\n");
out.push_str("declare void @print_bool(i1)\n\n");
out.push_str("declare i32 @string_len(ptr)\n\n");
out.push_str("declare ptr @__glagol_string_concat(ptr, ptr)\n\n");
out.push_str("declare i64 @__glagol_string_byte_at_result(ptr, i32)\n\n");
out.push_str("declare ptr @__glagol_string_slice_result(ptr, i32, i32)\n\n");
out.push_str("declare i1 @__glagol_string_starts_with(ptr, ptr)\n\n");
out.push_str("declare i1 @__glagol_string_ends_with(ptr, ptr)\n\n");
out.push_str("declare i64 @__glagol_string_parse_i32_result(ptr)\n\n");
out.push_str("declare i32 @__glagol_string_parse_i64_result(ptr, ptr)\n\n");
out.push_str("declare i64 @__glagol_string_parse_u32_result(ptr)\n\n");
out.push_str("declare i32 @__glagol_string_parse_u64_result(ptr, ptr)\n\n");
out.push_str("declare i32 @__glagol_string_parse_f64_result(ptr, ptr)\n\n");
out.push_str("declare i32 @__glagol_string_parse_bool_result(ptr, ptr)\n\n");
out.push_str("declare ptr @__glagol_json_quote_string(ptr)\n\n");
out.push_str("declare i32 @__glagol_json_parse_bool_value_result(ptr, ptr)\n\n");
out.push_str("declare i64 @__glagol_json_parse_i32_value_result(ptr)\n\n");
out.push_str("declare i64 @__glagol_json_parse_u32_value_result(ptr)\n\n");
out.push_str("declare i32 @__glagol_json_parse_i64_value_result(ptr, ptr)\n\n");
out.push_str("declare i32 @__glagol_json_parse_u64_value_result(ptr, ptr)\n\n");
out.push_str("declare i32 @__glagol_json_parse_f64_value_result(ptr, ptr)\n\n");
out.push_str("declare ptr @__glagol_num_i32_to_string(i32)\n\n");
out.push_str("declare ptr @__glagol_num_i64_to_string(i64)\n\n");
out.push_str("declare ptr @__glagol_num_u32_to_string(i32)\n\n");
out.push_str("declare ptr @__glagol_num_u64_to_string(i64)\n\n");
out.push_str("declare ptr @__glagol_num_f64_to_string(double)\n\n");
out.push_str("declare void @__glagol_io_eprint(ptr)\n\n");
out.push_str("declare ptr @__glagol_io_read_stdin_result()\n\n");
out.push_str("declare void @__glagol_process_init(i32, ptr)\n\n");
out.push_str("declare i32 @__glagol_process_argc()\n\n");
out.push_str("declare ptr @__glagol_process_arg(i32)\n\n");
out.push_str("declare ptr @__glagol_process_arg_result(i32)\n\n");
out.push_str("declare ptr @__glagol_env_get(ptr)\n\n");
out.push_str("declare ptr @__glagol_env_get_result(ptr)\n\n");
out.push_str("declare ptr @__glagol_fs_read_text(ptr)\n\n");
out.push_str("declare ptr @__glagol_fs_read_text_result(ptr)\n\n");
out.push_str("declare i32 @__glagol_fs_write_text(ptr, ptr)\n\n");
out.push_str("declare i32 @__glagol_fs_write_text_result(ptr, ptr)\n\n");
out.push_str("declare i1 @__glagol_fs_exists(ptr)\n\n");
out.push_str("declare i1 @__glagol_fs_is_file(ptr)\n\n");
out.push_str("declare i1 @__glagol_fs_is_dir(ptr)\n\n");
out.push_str("declare i32 @__glagol_fs_remove_file_result(ptr)\n\n");
out.push_str("declare i32 @__glagol_fs_create_dir_result(ptr)\n\n");
out.push_str("declare i64 @__glagol_fs_open_text_read_result(ptr)\n\n");
out.push_str("declare ptr @__glagol_fs_read_open_text_result(i32)\n\n");
out.push_str("declare i32 @__glagol_fs_close_result(i32)\n\n");
out.push_str("declare i64 @__glagol_net_tcp_connect_loopback_result(i32)\n\n");
out.push_str("declare i64 @__glagol_net_tcp_listen_loopback_result(i32)\n\n");
out.push_str("declare i64 @__glagol_net_tcp_bound_port_result(i32)\n\n");
out.push_str("declare i64 @__glagol_net_tcp_accept_result(i32)\n\n");
out.push_str("declare ptr @__glagol_net_tcp_read_all_result(i32)\n\n");
out.push_str("declare i32 @__glagol_net_tcp_write_text_result(i32, ptr)\n\n");
out.push_str("declare i32 @__glagol_net_tcp_close_result(i32)\n\n");
out.push_str("declare i1 @__glagol_string_eq(ptr, ptr)\n\n");
out.push_str("declare ptr @__glagol_vec_i32_empty()\n\n");
out.push_str("declare ptr @__glagol_vec_i32_append(ptr, i32)\n\n");
out.push_str("declare i32 @__glagol_vec_i32_len(ptr)\n\n");
out.push_str("declare i32 @__glagol_vec_i32_index(ptr, i32)\n\n");
out.push_str("declare i1 @__glagol_vec_i32_eq(ptr, ptr)\n\n");
out.push_str("declare ptr @__glagol_vec_i64_empty()\n\n");
out.push_str("declare ptr @__glagol_vec_i64_append(ptr, i64)\n\n");
out.push_str("declare i32 @__glagol_vec_i64_len(ptr)\n\n");
out.push_str("declare i64 @__glagol_vec_i64_index(ptr, i32)\n\n");
out.push_str("declare i1 @__glagol_vec_i64_eq(ptr, ptr)\n\n");
out.push_str("declare ptr @__glagol_vec_f64_empty()\n\n");
out.push_str("declare ptr @__glagol_vec_f64_append(ptr, double)\n\n");
out.push_str("declare i32 @__glagol_vec_f64_len(ptr)\n\n");
out.push_str("declare double @__glagol_vec_f64_index(ptr, i32)\n\n");
out.push_str("declare i1 @__glagol_vec_f64_eq(ptr, ptr)\n\n");
out.push_str("declare ptr @__glagol_vec_bool_empty()\n\n");
out.push_str("declare ptr @__glagol_vec_bool_append(ptr, i1)\n\n");
out.push_str("declare i32 @__glagol_vec_bool_len(ptr)\n\n");
out.push_str("declare i1 @__glagol_vec_bool_index(ptr, i32)\n\n");
out.push_str("declare i1 @__glagol_vec_bool_eq(ptr, ptr)\n\n");
out.push_str("declare ptr @__glagol_vec_string_empty()\n\n");
out.push_str("declare ptr @__glagol_vec_string_append(ptr, ptr)\n\n");
out.push_str("declare i32 @__glagol_vec_string_len(ptr)\n\n");
out.push_str("declare ptr @__glagol_vec_string_index(ptr, i32)\n\n");
out.push_str("declare i1 @__glagol_vec_string_eq(ptr, ptr)\n\n");
out.push_str("declare i32 @__glagol_time_monotonic_ms()\n\n");
out.push_str("declare void @__glagol_time_sleep_ms(i32)\n\n");
out.push_str("declare i32 @__glagol_random_i32()\n\n");
out.push_str("declare void @__glagol_array_bounds_trap()\n\n");
out.push_str("declare void @__glagol_unwrap_some_trap()\n\n");
out.push_str("declare void @__glagol_unwrap_ok_trap()\n\n");
out.push_str("declare void @__glagol_unwrap_err_trap()\n\n");
let mut declared_c_imports = HashSet::new();
for import in &program.c_imports {
if !declared_c_imports.insert(import.name.as_str()) {
continue;
}
let params = import
.params
.iter()
.map(|(_, ty)| ty.llvm())
.collect::<Vec<_>>()
.join(", ");
out.push_str(&format!(
"declare {} @{}({})\n\n",
import.return_type.llvm(),
import.name,
params
));
}
if !string_globals.definitions.is_empty() {
out.push_str(&string_globals.emit_definitions());
out.push('\n');
}
for function in &program.functions {
out.push_str(&emit_function(
function.file.as_str(),
function,
&layouts,
&enum_layouts,
&string_globals,
needs_process_runtime,
)?);
out.push('\n');
}
Ok(out)
}
#[derive(Clone)]
struct StructLayout {
fields: Vec<(String, Type)>,
}
type StructLayouts = HashMap<String, StructLayout>;
#[derive(Clone)]
struct EnumLayout {
payload_ty: Option<Type>,
}
type EnumLayouts = HashMap<String, EnumLayout>;
struct StringGlobals {
names: HashMap<String, String>,
definitions: Vec<StringGlobal>,
}
struct StringGlobal {
name: String,
value: String,
}
impl StringGlobals {
fn collect(program: &CheckedProgram) -> Self {
let mut globals = Self {
names: HashMap::new(),
definitions: Vec::new(),
};
for function in &program.functions {
for expr in &function.body {
globals.collect_expr(expr);
}
}
globals
}
fn collect_expr(&mut self, expr: &TExpr) {
match &expr.kind {
TExprKind::String(value) => {
self.intern(value);
}
TExprKind::EnumVariant { payload, .. } => {
if let Some(payload) = payload {
self.collect_expr(payload);
}
}
TExprKind::StructInit { fields, .. } => {
for (_, value) in fields {
self.collect_expr(value);
}
}
TExprKind::ArrayInit { elements } => {
for element in elements {
self.collect_expr(element);
}
}
TExprKind::OptionSome { value }
| TExprKind::ResultOk { value }
| TExprKind::ResultErr { value }
| TExprKind::OptionIsSome { value }
| TExprKind::OptionIsNone { value }
| TExprKind::OptionUnwrapSome { value }
| TExprKind::ResultIsOk { value, .. }
| TExprKind::ResultIsErr { value, .. }
| TExprKind::ResultUnwrapOk { value, .. }
| TExprKind::ResultUnwrapErr { value, .. }
| TExprKind::FieldAccess { value, .. } => self.collect_expr(value),
TExprKind::Index { array, index } => {
self.collect_expr(array);
self.collect_expr(index);
}
TExprKind::Local { initializer, .. } => self.collect_expr(initializer),
TExprKind::Set { expr, .. } => self.collect_expr(expr),
TExprKind::Binary { left, right, .. } => {
self.collect_expr(left);
self.collect_expr(right);
}
TExprKind::If {
condition,
then_expr,
else_expr,
} => {
self.collect_expr(condition);
self.collect_expr(then_expr);
self.collect_expr(else_expr);
}
TExprKind::Match { subject, arms } => {
self.collect_expr(subject);
for arm in arms {
for expr in &arm.body {
self.collect_expr(expr);
}
}
}
TExprKind::While { condition, body } => {
self.collect_expr(condition);
for expr in body {
self.collect_expr(expr);
}
}
TExprKind::Unsafe { body } => {
for expr in body {
self.collect_expr(expr);
}
}
TExprKind::Call { args, .. } => {
for arg in args {
self.collect_expr(arg);
}
}
TExprKind::Int(_)
| TExprKind::Int64(_)
| TExprKind::UInt32(_)
| TExprKind::UInt64(_)
| TExprKind::Float(_)
| TExprKind::Bool(_)
| TExprKind::Var(_)
| TExprKind::OptionNone => {}
}
}
fn intern(&mut self, value: &str) {
if self.names.contains_key(value) {
return;
}
let name = format!(".str.{}", self.definitions.len());
self.names.insert(value.to_string(), name.clone());
self.definitions.push(StringGlobal {
name,
value: value.to_string(),
});
}
fn global_name(&self, value: &str) -> Option<&str> {
self.names.get(value).map(String::as_str)
}
fn emit_definitions(&self) -> String {
let mut out = String::new();
for global in &self.definitions {
out.push_str(&format!(
"@{} = private unnamed_addr constant [{} x i8] c\"{}\", align 1\n",
global.name,
global.value.len() + 1,
llvm_c_string(&global.value)
));
}
out
}
}
fn llvm_c_string(value: &str) -> String {
let mut out = String::new();
for byte in value.bytes().chain(std::iter::once(0)) {
match byte {
b'"' => out.push_str("\\22"),
b'\\' => out.push_str("\\5C"),
b'\n' => out.push_str("\\0A"),
b'\r' => out.push_str("\\0D"),
b'\t' => out.push_str("\\09"),
0x20..=0x7e => out.push(byte as char),
_ => out.push_str(&format!("\\{:02X}", byte)),
}
}
out
}
fn struct_layouts(program: &CheckedProgram) -> StructLayouts {
program
.structs
.iter()
.map(|decl| {
(
decl.name.clone(),
StructLayout {
fields: decl.fields.clone(),
},
)
})
.collect()
}
fn enum_layouts(program: &CheckedProgram) -> EnumLayouts {
program
.enums
.iter()
.map(|decl| {
(
decl.name.clone(),
EnumLayout {
payload_ty: decl
.variants
.iter()
.find_map(|variant| variant.payload_ty.clone()),
},
)
})
.collect()
}
fn needs_process_runtime(program: &CheckedProgram) -> bool {
program
.functions
.iter()
.flat_map(|function| function.body.iter())
.any(expr_needs_process_runtime)
}
fn expr_needs_process_runtime(expr: &TExpr) -> bool {
match &expr.kind {
TExprKind::StructInit { fields, .. } => fields
.iter()
.any(|(_, value)| expr_needs_process_runtime(value)),
TExprKind::ArrayInit { elements } => elements.iter().any(expr_needs_process_runtime),
TExprKind::OptionSome { value }
| TExprKind::ResultOk { value }
| TExprKind::ResultErr { value }
| TExprKind::OptionIsSome { value }
| TExprKind::OptionIsNone { value }
| TExprKind::OptionUnwrapSome { value }
| TExprKind::ResultIsOk { value, .. }
| TExprKind::ResultIsErr { value, .. }
| TExprKind::ResultUnwrapOk { value, .. }
| TExprKind::ResultUnwrapErr { value, .. }
| TExprKind::FieldAccess { value, .. } => expr_needs_process_runtime(value),
TExprKind::Index { array, index } => {
expr_needs_process_runtime(array) || expr_needs_process_runtime(index)
}
TExprKind::Local { initializer, .. } => expr_needs_process_runtime(initializer),
TExprKind::Set { expr, .. } => expr_needs_process_runtime(expr),
TExprKind::Binary { left, right, .. } => {
expr_needs_process_runtime(left) || expr_needs_process_runtime(right)
}
TExprKind::If {
condition,
then_expr,
else_expr,
} => {
expr_needs_process_runtime(condition)
|| expr_needs_process_runtime(then_expr)
|| expr_needs_process_runtime(else_expr)
}
TExprKind::Match { subject, arms } => {
expr_needs_process_runtime(subject)
|| arms
.iter()
.flat_map(|arm| arm.body.iter())
.any(expr_needs_process_runtime)
}
TExprKind::While { condition, body } => {
expr_needs_process_runtime(condition) || body.iter().any(expr_needs_process_runtime)
}
TExprKind::Unsafe { body } => body.iter().any(expr_needs_process_runtime),
TExprKind::Call { name, args } => {
matches!(
std_runtime::runtime_symbol(name),
Some(
"__glagol_process_argc"
| "__glagol_process_arg"
| "__glagol_process_arg_result"
)
) || args.iter().any(expr_needs_process_runtime)
}
TExprKind::Int(_)
| TExprKind::Int64(_)
| TExprKind::UInt32(_)
| TExprKind::UInt64(_)
| TExprKind::Float(_)
| TExprKind::Bool(_)
| TExprKind::String(_)
| TExprKind::Var(_)
| TExprKind::OptionNone => false,
TExprKind::EnumVariant { payload, .. } => payload
.as_deref()
.map(expr_needs_process_runtime)
.unwrap_or(false),
}
}
fn emit_function(
file: &str,
function: &CheckedFunction,
layouts: &StructLayouts,
enum_layouts: &EnumLayouts,
string_globals: &StringGlobals,
needs_process_runtime: bool,
) -> Result<String, Vec<Diagnostic>> {
validate_function_signature(file, function, layouts, enum_layouts)?;
let mut gen = FunctionGen {
next_reg: 0,
next_block: 0,
local_alloc_counts: HashMap::new(),
current_block: "entry".to_string(),
locals: HashMap::new(),
lines: Vec::new(),
file: file.to_string(),
layouts,
enum_layouts,
string_globals,
};
for (name, ty) in &function.params {
match ty {
Type::Array(inner, len)
if llvm_fixed_array_type(layouts, enum_layouts, inner, *len).is_some() =>
{
let llvm_ty = llvm_type(layouts, enum_layouts, ty).expect("validated array type");
let ptr = gen.local_addr(name);
gen.lines.push(format!("{} = alloca {}", ptr, llvm_ty));
gen.lines
.push(format!("store {} %{}, ptr {}", llvm_ty, name, ptr));
gen.locals.insert(
name.clone(),
LocalValue::Array {
ptr,
ty: ty.clone(),
},
);
}
_ => {
gen.locals
.insert(name.clone(), LocalValue::Value(format!("%{}", name)));
}
}
}
let is_entry_main =
needs_process_runtime && function.name == "main" && function.params.is_empty();
let params = if is_entry_main {
"i32 %__glagol_argc, ptr %__glagol_argv".to_string()
} else {
function
.params
.iter()
.map(|(name, ty)| {
format!(
"{} %{}",
llvm_type(layouts, enum_layouts, ty).expect("validated type"),
name
)
})
.collect::<Vec<_>>()
.join(", ")
};
let return_ty =
llvm_type(layouts, enum_layouts, &function.return_type).expect("validated return type");
let mut out = String::new();
out.push_str(&format!(
"define {} @{}({}) {{\n",
return_ty, function.name, params
));
out.push_str("entry:\n");
if is_entry_main {
gen.lines.push(
"call void @__glagol_process_init(i32 %__glagol_argc, ptr %__glagol_argv)".to_string(),
);
}
let mut last = None;
for expr in &function.body {
match gen.emit_expr(expr) {
Ok(value) => last = Some(value),
Err(err) => return Err(vec![err]),
}
}
for line in gen.lines {
if !line.ends_with(':') {
out.push_str(" ");
}
out.push_str(&line);
out.push('\n');
}
match function.return_type {
Type::Unit => out.push_str(" ret void\n"),
_ => {
let value = last.unwrap_or_else(|| "0".to_string());
let return_ty = llvm_type(layouts, enum_layouts, &function.return_type)
.expect("validated return type");
out.push_str(&format!(" ret {} {}\n", return_ty, value));
}
}
out.push_str("}\n");
Ok(out)
}
fn validate_function_signature(
file: &str,
function: &CheckedFunction,
layouts: &StructLayouts,
enum_layouts: &EnumLayouts,
) -> Result<(), Vec<Diagnostic>> {
let mut errors = Vec::new();
let mut reported_types = HashSet::new();
for (_, ty) in &function.params {
if !is_backend_param_type_supported(ty, layouts, enum_layouts)
&& reported_types.insert(ty.to_string())
{
errors.push(unsupported_type(file, function, ty));
}
}
if !is_backend_return_type_supported(&function.return_type, layouts, enum_layouts)
&& reported_types.insert(function.return_type.to_string())
{
errors.push(unsupported_type(file, function, &function.return_type));
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn is_backend_param_type_supported(
ty: &Type,
layouts: &StructLayouts,
enum_layouts: &EnumLayouts,
) -> bool {
match ty {
Type::I32
| Type::I64
| Type::U32
| Type::U64
| Type::F64
| Type::Bool
| Type::Unit
| Type::String => true,
Type::Named(_) => true,
Type::Array(inner, len) => {
llvm_fixed_array_type(layouts, enum_layouts, inner, *len).is_some()
}
Type::Vec(inner) => {
**inner == Type::I32
|| **inner == Type::I64
|| **inner == Type::F64
|| **inner == Type::Bool
|| **inner == Type::String
}
Type::Option(inner) => {
**inner == Type::I32
|| **inner == Type::I64
|| **inner == Type::U32
|| **inner == Type::U64
|| **inner == Type::F64
|| **inner == Type::Bool
|| **inner == Type::String
}
Type::Result(ok, err) => result_type_supported(ok, err),
_ => false,
}
}
fn is_backend_return_type_supported(
ty: &Type,
layouts: &StructLayouts,
enum_layouts: &EnumLayouts,
) -> bool {
match ty {
Type::I32
| Type::I64
| Type::U32
| Type::U64
| Type::F64
| Type::Bool
| Type::Unit
| Type::String => true,
Type::Named(_) => true,
Type::Array(inner, len) => {
llvm_fixed_array_type(layouts, enum_layouts, inner, *len).is_some()
}
Type::Vec(inner) => {
**inner == Type::I32
|| **inner == Type::I64
|| **inner == Type::F64
|| **inner == Type::Bool
|| **inner == Type::String
}
Type::Option(inner) => {
**inner == Type::I32
|| **inner == Type::I64
|| **inner == Type::U32
|| **inner == Type::U64
|| **inner == Type::F64
|| **inner == Type::Bool
|| **inner == Type::String
}
Type::Result(ok, err) => result_type_supported(ok, err),
_ => false,
}
}
fn llvm_type(layouts: &StructLayouts, enum_layouts: &EnumLayouts, ty: &Type) -> Option<String> {
match ty {
Type::Array(inner, len) => llvm_fixed_array_type(layouts, enum_layouts, inner, *len),
Type::Named(name) => {
if let Some(layout) = enum_layouts.get(name) {
return Some(if let Some(payload_ty) = &layout.payload_ty {
format!(
"{{ i32, {} }}",
llvm_type(layouts, enum_layouts, payload_ty)?
)
} else {
"i32".to_string()
});
}
let Some(layout) = layouts.get(name) else {
return Some("i32".to_string());
};
let fields = layout
.fields
.iter()
.map(|(_, ty)| llvm_type(layouts, enum_layouts, ty))
.collect::<Option<Vec<_>>>()?
.join(", ");
Some(format!("{{ {} }}", fields))
}
_ => Some(ty.llvm().to_string()),
}
}
fn llvm_fixed_array_type(
layouts: &StructLayouts,
enum_layouts: &EnumLayouts,
inner: &Type,
len: usize,
) -> Option<String> {
if len == 0 {
return None;
}
let inner_ty = match inner {
Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String => {
inner.llvm().to_string()
}
Type::Named(_) => llvm_type(layouts, enum_layouts, inner)?,
_ => return None,
};
Some(format!("[{} x {}]", len, inner_ty))
}
fn format_f64_literal(value: f64) -> String {
format!("{:.17}", value)
}
fn is_vec_i32_type(ty: &Type) -> bool {
matches!(ty, Type::Vec(inner) if **inner == Type::I32)
}
fn is_vec_i64_type(ty: &Type) -> bool {
matches!(ty, Type::Vec(inner) if **inner == Type::I64)
}
fn is_vec_f64_type(ty: &Type) -> bool {
matches!(ty, Type::Vec(inner) if **inner == Type::F64)
}
fn is_vec_bool_type(ty: &Type) -> bool {
matches!(ty, Type::Vec(inner) if **inner == Type::Bool)
}
fn is_vec_string_type(ty: &Type) -> bool {
matches!(ty, Type::Vec(inner) if **inner == Type::String)
}
fn vec_eq_runtime_symbol(ty: &Type) -> Option<&'static str> {
if is_vec_i32_type(ty) {
Some("__glagol_vec_i32_eq")
} else if is_vec_i64_type(ty) {
Some("__glagol_vec_i64_eq")
} else if is_vec_f64_type(ty) {
Some("__glagol_vec_f64_eq")
} else if is_vec_bool_type(ty) {
Some("__glagol_vec_bool_eq")
} else if is_vec_string_type(ty) {
Some("__glagol_vec_string_eq")
} else {
None
}
}
fn result_type_supported(ok: &Type, err: &Type) -> bool {
matches!(
(ok, err),
(Type::I32, Type::I32)
| (Type::I64, Type::I32)
| (Type::U32, Type::I32)
| (Type::U64, Type::I32)
| (Type::F64, Type::I32)
| (Type::Bool, Type::I32)
| (Type::String, Type::I32)
)
}
fn zero_value_for_type(ty: &Type) -> Option<String> {
match ty {
Type::I32 => Some("0".to_string()),
Type::I64 => Some("0".to_string()),
Type::U32 => Some("0".to_string()),
Type::U64 => Some("0".to_string()),
Type::F64 => Some("0.0".to_string()),
Type::Bool => Some("0".to_string()),
Type::String => Some("null".to_string()),
Type::Array(_, _) | Type::Named(_) => Some("zeroinitializer".to_string()),
_ => None,
}
}
fn result_payload_index(ty: &Type, ok_payload: bool) -> Option<usize> {
match ty {
Type::Option(inner)
if (**inner == Type::I32
|| **inner == Type::I64
|| **inner == Type::U32
|| **inner == Type::U64
|| **inner == Type::F64
|| **inner == Type::Bool
|| **inner == Type::String)
&& ok_payload =>
{
Some(1)
}
Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => Some(1),
Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => {
Some(if ok_payload { 1 } else { 2 })
}
Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => Some(1),
Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => {
Some(if ok_payload { 1 } else { 2 })
}
Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => {
Some(if ok_payload { 1 } else { 2 })
}
Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => {
Some(if ok_payload { 1 } else { 2 })
}
Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => {
Some(if ok_payload { 1 } else { 2 })
}
_ => None,
}
}
fn unsupported_type(file: &str, function: &CheckedFunction, ty: &Type) -> Diagnostic {
Diagnostic::new(
file,
"UnsupportedBackendFeature",
format!(
"backend does not support `{}` in function `{}` signature",
ty, function.name
),
)
.with_span(function.span)
.hint(
"keep this type in speculative examples until layout and LLVM ABI behavior are implemented",
)
}
struct FunctionGen<'a> {
next_reg: usize,
next_block: usize,
local_alloc_counts: HashMap<String, usize>,
current_block: String,
locals: HashMap<String, LocalValue>,
lines: Vec<String>,
file: String,
layouts: &'a StructLayouts,
enum_layouts: &'a EnumLayouts,
string_globals: &'a StringGlobals,
}
#[derive(Clone)]
enum LocalValue {
Value(String),
Ptr { ptr: String, ty: Type },
Array { ptr: String, ty: Type },
}
impl FunctionGen<'_> {
fn reg(&mut self) -> String {
let reg = format!("%{}", self.next_reg);
self.next_reg += 1;
reg
}
fn block(&mut self, prefix: &str) -> String {
let block = format!("{}.{}", prefix, self.next_block);
self.next_block += 1;
block
}
fn local_addr(&mut self, name: &str) -> String {
let count = self.local_alloc_counts.entry(name.to_string()).or_insert(0);
let ptr = if *count == 0 {
format!("%{}.addr", name)
} else {
format!("%{}.addr.{}", name, count)
};
*count += 1;
ptr
}
fn label(&mut self, block: &str) {
self.lines.push(format!("{}:", block));
self.current_block = block.to_string();
}
fn unsupported(&self, expr: &TExpr, feature: &str) -> Diagnostic {
Diagnostic::new(
&self.file,
"UnsupportedBackendFeature",
format!("backend does not support {}", feature),
)
.with_span(expr.span)
.hint("keep this form in speculative examples until LLVM lowering is implemented")
}
fn llvm_type(&self, expr: &TExpr, ty: &Type) -> Result<String, Diagnostic> {
llvm_type(self.layouts, self.enum_layouts, ty).ok_or_else(|| {
self.unsupported(expr, &format!("LLVM representation for type `{}`", ty))
})
}
fn emit_numeric_widening_conversion(
&mut self,
expr: &TExpr,
args: &[TExpr],
instruction: &str,
from_ty: &str,
to_ty: &str,
) -> Result<String, Diagnostic> {
let [arg] = args else {
return Err(self.unsupported(expr, "malformed numeric widening conversion"));
};
let value = self.emit_expr(arg)?;
let reg = self.reg();
self.lines.push(format!(
"{} = {} {} {} to {}",
reg, instruction, from_ty, value, to_ty
));
Ok(reg)
}
fn emit_i64_to_i32_result_conversion(
&mut self,
expr: &TExpr,
args: &[TExpr],
) -> Result<String, Diagnostic> {
let [arg] = args else {
return Err(self.unsupported(expr, "malformed checked i64-to-i32 conversion"));
};
let value = self.emit_expr(arg)?;
let above_min = self.reg();
self.lines.push(format!(
"{} = icmp sge i64 {}, -2147483648",
above_min, value
));
let below_max = self.reg();
self.lines.push(format!(
"{} = icmp sle i64 {}, 2147483647",
below_max, value
));
let in_range = self.reg();
self.lines.push(format!(
"{} = and i1 {}, {}",
in_range, above_min, below_max
));
let narrowed = self.reg();
self.lines
.push(format!("{} = trunc i64 {} to i32", narrowed, value));
let payload = self.reg();
self.lines.push(format!(
"{} = select i1 {}, i32 {}, i32 1",
payload, in_range, narrowed
));
self.emit_tagged_i32_aggregate_from_values(expr, &in_range, &payload)
}
fn emit_f64_to_i32_result_conversion(
&mut self,
expr: &TExpr,
args: &[TExpr],
) -> Result<String, Diagnostic> {
let [arg] = args else {
return Err(self.unsupported(expr, "malformed checked f64-to-i32 conversion"));
};
let value = self.emit_expr(arg)?;
let above_min = self.reg();
self.lines.push(format!(
"{} = fcmp oge double {}, -2147483648.0",
above_min, value
));
let below_max = self.reg();
self.lines.push(format!(
"{} = fcmp ole double {}, 2147483647.0",
below_max, value
));
let in_range = self.reg();
self.lines.push(format!(
"{} = and i1 {}, {}",
in_range, above_min, below_max
));
let integral_block = self.block("f64.to_i32.integral");
let exponent_block = self.block("f64.to_i32.exponent");
let fraction_block = self.block("f64.to_i32.fraction");
let convert_block = self.block("f64.to_i32.convert");
let err_block = self.block("f64.to_i32.err");
let merge_block = self.block("f64.to_i32.end");
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
in_range, integral_block, err_block
));
self.label(&integral_block);
let bits = self.reg();
self.lines
.push(format!("{} = bitcast double {} to i64", bits, value));
let abs_bits = self.reg();
self.lines.push(format!(
"{} = and i64 {}, 9223372036854775807",
abs_bits, bits
));
let is_zero = self.reg();
self.lines
.push(format!("{} = icmp eq i64 {}, 0", is_zero, abs_bits));
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
is_zero, convert_block, exponent_block
));
self.label(&exponent_block);
let exponent = self.reg();
self.lines
.push(format!("{} = lshr i64 {}, 52", exponent, abs_bits));
let exponent_ge_bias = self.reg();
self.lines.push(format!(
"{} = icmp uge i64 {}, 1023",
exponent_ge_bias, exponent
));
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
exponent_ge_bias, fraction_block, err_block
));
self.label(&fraction_block);
let unbiased_exponent = self.reg();
self.lines.push(format!(
"{} = sub i64 {}, 1023",
unbiased_exponent, exponent
));
let fractional_width = self.reg();
self.lines.push(format!(
"{} = sub i64 52, {}",
fractional_width, unbiased_exponent
));
let fractional_one = self.reg();
self.lines.push(format!(
"{} = shl i64 1, {}",
fractional_one, fractional_width
));
let fractional_mask = self.reg();
self.lines.push(format!(
"{} = sub i64 {}, 1",
fractional_mask, fractional_one
));
let fractional_bits = self.reg();
self.lines.push(format!(
"{} = and i64 {}, {}",
fractional_bits, abs_bits, fractional_mask
));
let integral = self.reg();
self.lines
.push(format!("{} = icmp eq i64 {}, 0", integral, fractional_bits));
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
integral, convert_block, err_block
));
self.label(&convert_block);
let narrowed = self.reg();
self.lines
.push(format!("{} = fptosi double {} to i32", narrowed, value));
let ok_result = self.emit_tagged_i32_aggregate_from_values(expr, "true", &narrowed)?;
let ok_pred = self.current_block.clone();
self.lines.push(format!("br label %{}", merge_block));
self.label(&err_block);
let err_result = self.emit_tagged_i32_aggregate_from_values(expr, "false", "1")?;
let err_pred = self.current_block.clone();
self.lines.push(format!("br label %{}", merge_block));
self.label(&merge_block);
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let result = self.reg();
self.lines.push(format!(
"{} = phi {} [ {}, %{} ], [ {}, %{} ]",
result, aggregate_ty, ok_result, ok_pred, err_result, err_pred
));
Ok(result)
}
fn emit_f64_to_i64_result_conversion(
&mut self,
expr: &TExpr,
args: &[TExpr],
) -> Result<String, Diagnostic> {
let [arg] = args else {
return Err(self.unsupported(expr, "malformed checked f64-to-i64 conversion"));
};
let value = self.emit_expr(arg)?;
let above_min = self.reg();
self.lines.push(format!(
"{} = fcmp oge double {}, -9223372036854775808.0",
above_min, value
));
let below_max = self.reg();
self.lines.push(format!(
"{} = fcmp olt double {}, 9223372036854775808.0",
below_max, value
));
let in_range = self.reg();
self.lines.push(format!(
"{} = and i1 {}, {}",
in_range, above_min, below_max
));
let integral_block = self.block("f64.to_i64.integral");
let exponent_block = self.block("f64.to_i64.exponent");
let mantissa_block = self.block("f64.to_i64.mantissa");
let fraction_block = self.block("f64.to_i64.fraction");
let convert_block = self.block("f64.to_i64.convert");
let err_block = self.block("f64.to_i64.err");
let merge_block = self.block("f64.to_i64.end");
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
in_range, integral_block, err_block
));
self.label(&integral_block);
let bits = self.reg();
self.lines
.push(format!("{} = bitcast double {} to i64", bits, value));
let abs_bits = self.reg();
self.lines.push(format!(
"{} = and i64 {}, 9223372036854775807",
abs_bits, bits
));
let is_zero = self.reg();
self.lines
.push(format!("{} = icmp eq i64 {}, 0", is_zero, abs_bits));
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
is_zero, convert_block, exponent_block
));
self.label(&exponent_block);
let exponent = self.reg();
self.lines
.push(format!("{} = lshr i64 {}, 52", exponent, abs_bits));
let exponent_ge_bias = self.reg();
self.lines.push(format!(
"{} = icmp uge i64 {}, 1023",
exponent_ge_bias, exponent
));
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
exponent_ge_bias, mantissa_block, err_block
));
self.label(&mantissa_block);
let unbiased_exponent = self.reg();
self.lines.push(format!(
"{} = sub i64 {}, 1023",
unbiased_exponent, exponent
));
let no_fractional_bits = self.reg();
self.lines.push(format!(
"{} = icmp uge i64 {}, 52",
no_fractional_bits, unbiased_exponent
));
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
no_fractional_bits, convert_block, fraction_block
));
self.label(&fraction_block);
let fractional_width = self.reg();
self.lines.push(format!(
"{} = sub i64 52, {}",
fractional_width, unbiased_exponent
));
let fractional_one = self.reg();
self.lines.push(format!(
"{} = shl i64 1, {}",
fractional_one, fractional_width
));
let fractional_mask = self.reg();
self.lines.push(format!(
"{} = sub i64 {}, 1",
fractional_mask, fractional_one
));
let fractional_bits = self.reg();
self.lines.push(format!(
"{} = and i64 {}, {}",
fractional_bits, abs_bits, fractional_mask
));
let integral = self.reg();
self.lines
.push(format!("{} = icmp eq i64 {}, 0", integral, fractional_bits));
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
integral, convert_block, err_block
));
self.label(&convert_block);
let narrowed = self.reg();
self.lines
.push(format!("{} = fptosi double {} to i64", narrowed, value));
let ok_result = self.emit_i64_result_aggregate(expr, "true", &narrowed, "0")?;
let ok_pred = self.current_block.clone();
self.lines.push(format!("br label %{}", merge_block));
self.label(&err_block);
let err_result = self.emit_i64_result_aggregate(expr, "false", "0", "1")?;
let err_pred = self.current_block.clone();
self.lines.push(format!("br label %{}", merge_block));
self.label(&merge_block);
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let result = self.reg();
self.lines.push(format!(
"{} = phi {} [ {}, %{} ], [ {}, %{} ]",
result, aggregate_ty, ok_result, ok_pred, err_result, err_pred
));
Ok(result)
}
fn is_payload_enum_type(&self, ty: &Type) -> bool {
matches!(
ty,
Type::Named(name)
if self
.enum_layouts
.get(name)
.and_then(|layout| layout.payload_ty.as_ref())
.is_some()
)
}
fn enum_payload_type<'b>(&'b self, ty: &Type) -> Option<&'b Type> {
match ty {
Type::Named(name) => self
.enum_layouts
.get(name)
.and_then(|layout| layout.payload_ty.as_ref()),
_ => None,
}
}
fn emit_equality_for_type(
&mut self,
ty: &Type,
left: &str,
right: &str,
) -> Result<String, Diagnostic> {
let reg = self.reg();
match ty {
Type::String => self.lines.push(format!(
"{} = call i1 @__glagol_string_eq(ptr {}, ptr {})",
reg, left, right
)),
Type::F64 => self
.lines
.push(format!("{} = fcmp oeq double {}, {}", reg, left, right)),
Type::Bool => self
.lines
.push(format!("{} = icmp eq i1 {}, {}", reg, left, right)),
Type::U64 => self
.lines
.push(format!("{} = icmp eq i64 {}, {}", reg, left, right)),
Type::I64 => self
.lines
.push(format!("{} = icmp eq i64 {}, {}", reg, left, right)),
Type::U32 => self
.lines
.push(format!("{} = icmp eq i32 {}, {}", reg, left, right)),
Type::I32 => self
.lines
.push(format!("{} = icmp eq i32 {}, {}", reg, left, right)),
_ => {
return Err(Diagnostic::new(
&self.file,
"UnsupportedBackendFeature",
format!(
"backend does not support equality for enum payload type `{}`",
ty
),
))
}
}
Ok(reg)
}
fn enum_payload_zero_value(&self, ty: &Type) -> Result<String, Diagnostic> {
zero_value_for_type(ty).ok_or_else(|| {
Diagnostic::new(
&self.file,
"UnsupportedBackendFeature",
format!("backend does not support enum payload type `{}`", ty),
)
})
}
fn enum_payload_llvm_type(&self, ty: &Type) -> Result<String, Diagnostic> {
llvm_type(self.layouts, self.enum_layouts, ty).ok_or_else(|| {
Diagnostic::new(
&self.file,
"UnsupportedBackendFeature",
format!("backend does not support enum payload type `{}`", ty),
)
})
}
fn emit_payload_enum_equality(
&mut self,
reg: &str,
ty: &Type,
left_aggregate_ty: &str,
left_value: &str,
right_value: &str,
) -> Result<(), Diagnostic> {
let left_tag = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, 0",
left_tag, left_aggregate_ty, left_value
));
let right_tag = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, 0",
right_tag, left_aggregate_ty, right_value
));
let tags_equal = self.reg();
self.lines.push(format!(
"{} = icmp eq i32 {}, {}",
tags_equal, left_tag, right_tag
));
let left_payload = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, 1",
left_payload, left_aggregate_ty, left_value
));
let right_payload = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, 1",
right_payload, left_aggregate_ty, right_value
));
let payloads_equal = self.emit_equality_for_type(ty, &left_payload, &right_payload)?;
self.lines.push(format!(
"{} = and i1 {}, {}",
reg, tags_equal, payloads_equal
));
Ok(())
}
fn emit_expr(&mut self, expr: &TExpr) -> Result<String, Diagnostic> {
match &expr.kind {
TExprKind::Int(value) => Ok(value.to_string()),
TExprKind::Int64(value) => Ok(value.to_string()),
TExprKind::UInt32(value) => Ok(value.to_string()),
TExprKind::UInt64(value) => Ok(value.to_string()),
TExprKind::Float(value) => Ok(format_f64_literal(*value)),
TExprKind::Bool(value) => Ok(if *value {
"1".to_string()
} else {
"0".to_string()
}),
TExprKind::String(value) => self
.string_globals
.global_name(value)
.map(|name| format!("@{}", name))
.ok_or_else(|| self.unsupported(expr, "string literal storage")),
TExprKind::EnumVariant {
enum_name,
discriminant,
payload,
..
} => self.emit_enum_variant(expr, enum_name, *discriminant, payload.as_deref()),
TExprKind::StructInit { name, fields } => self.emit_struct_init(expr, name, fields),
TExprKind::ArrayInit { elements } => self.emit_array_init(expr, elements),
TExprKind::OptionSome { value } => self.emit_option_aggregate(expr, true, Some(value)),
TExprKind::OptionNone => self.emit_option_aggregate(expr, false, None),
TExprKind::ResultOk { value } => self.emit_result_aggregate(expr, true, value),
TExprKind::ResultErr { value } => self.emit_result_aggregate(expr, false, value),
TExprKind::OptionIsSome { value } => self.emit_tag_observer(expr, value, false),
TExprKind::OptionIsNone { value } => self.emit_tag_observer(expr, value, true),
TExprKind::OptionUnwrapSome { value } => {
self.emit_tag_checked_payload_access(expr, value, true, "__glagol_unwrap_some_trap")
}
TExprKind::ResultIsOk { value, .. } => self.emit_tag_observer(expr, value, false),
TExprKind::ResultIsErr { value, .. } => self.emit_tag_observer(expr, value, true),
TExprKind::ResultUnwrapOk { value, .. } => {
self.emit_tag_checked_payload_access(expr, value, true, "__glagol_unwrap_ok_trap")
}
TExprKind::ResultUnwrapErr { value, .. } => {
self.emit_tag_checked_payload_access(expr, value, false, "__glagol_unwrap_err_trap")
}
TExprKind::FieldAccess { value, field } => self.emit_field_access(expr, value, field),
TExprKind::Index { array, index } => self.emit_index(expr, array, index),
TExprKind::Var(name) => match self.locals.get(name).cloned() {
Some(LocalValue::Value(value)) => Ok(value),
Some(LocalValue::Ptr { ptr, ty }) => {
let reg = self.reg();
let llvm_ty = self.llvm_type(expr, &ty)?;
self.lines
.push(format!("{} = load {}, ptr {}", reg, llvm_ty, ptr));
Ok(reg)
}
Some(LocalValue::Array { ptr, ty }) => {
let reg = self.reg();
let llvm_ty = self.llvm_type(expr, &ty)?;
self.lines
.push(format!("{} = load {}, ptr {}", reg, llvm_ty, ptr));
Ok(reg)
}
None => Ok(format!("%{}", name)),
},
TExprKind::Local {
mutable,
name,
initializer,
} => match &initializer.ty {
Type::I32 | Type::Bool | Type::I64 | Type::U32 | Type::U64 | Type::F64
if !*mutable =>
{
self.emit_ssa_local(name, initializer)
}
Type::I32 | Type::Bool | Type::I64 | Type::U32 | Type::U64 | Type::F64 => {
self.emit_value_local(name, initializer)
}
Type::String => self.emit_value_local(name, initializer),
Type::Vec(inner)
if (**inner == Type::I32
|| **inner == Type::I64
|| **inner == Type::F64
|| **inner == Type::Bool
|| **inner == Type::String) =>
{
self.emit_value_local(name, initializer)
}
Type::Option(inner)
if (**inner == Type::I32
|| **inner == Type::I64
|| **inner == Type::U32
|| **inner == Type::U64
|| **inner == Type::F64
|| **inner == Type::Bool
|| **inner == Type::String) =>
{
self.emit_value_local(name, initializer)
}
Type::Result(ok, err) if result_type_supported(ok, err) => {
self.emit_value_local(name, initializer)
}
Type::Named(struct_name) if self.layouts.contains_key(struct_name) => {
self.emit_value_local(name, initializer)
}
Type::Named(_) => self.emit_value_local(name, initializer),
Type::Array(_, _) if !*mutable => self.emit_array_local(expr, name, initializer),
_ => Err(self.unsupported(expr, "unsupported local variables")),
},
TExprKind::Set { name, expr: value } => {
let rhs = self.emit_expr(value)?;
let Some(LocalValue::Ptr { ptr, ty }) = self.locals.get(name).cloned() else {
return Err(self.unsupported(expr, "assignment to non-local values"));
};
let llvm_ty = self.llvm_type(expr, &ty)?;
self.lines
.push(format!("store {} {}, ptr {}", llvm_ty, rhs, ptr));
Ok("0".to_string())
}
TExprKind::Binary { op, left, right } => {
let l = self.emit_expr(left)?;
let r = self.emit_expr(right)?;
let reg = self.reg();
match op {
BinaryOp::Add if left.ty == Type::F64 => self
.lines
.push(format!("{} = fadd double {}, {}", reg, l, r)),
BinaryOp::Sub if left.ty == Type::F64 => self
.lines
.push(format!("{} = fsub double {}, {}", reg, l, r)),
BinaryOp::Mul if left.ty == Type::F64 => self
.lines
.push(format!("{} = fmul double {}, {}", reg, l, r)),
BinaryOp::Div if left.ty == Type::F64 => self
.lines
.push(format!("{} = fdiv double {}, {}", reg, l, r)),
BinaryOp::Rem if left.ty == Type::F64 => {
return Err(self.unsupported(expr, "f64 remainder"))
}
BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::BitXor
if left.ty == Type::F64 =>
{
return Err(self.unsupported(expr, "f64 bitwise operation"))
}
BinaryOp::Add if left.ty == Type::I64 => {
self.lines.push(format!("{} = add i64 {}, {}", reg, l, r))
}
BinaryOp::Add if left.ty == Type::U64 => {
self.lines.push(format!("{} = add i64 {}, {}", reg, l, r))
}
BinaryOp::Sub if left.ty == Type::I64 => {
self.lines.push(format!("{} = sub i64 {}, {}", reg, l, r))
}
BinaryOp::Sub if left.ty == Type::U64 => {
self.lines.push(format!("{} = sub i64 {}, {}", reg, l, r))
}
BinaryOp::Mul if left.ty == Type::I64 => {
self.lines.push(format!("{} = mul i64 {}, {}", reg, l, r))
}
BinaryOp::Mul if left.ty == Type::U64 => {
self.lines.push(format!("{} = mul i64 {}, {}", reg, l, r))
}
BinaryOp::Div if left.ty == Type::I64 => {
self.lines.push(format!("{} = sdiv i64 {}, {}", reg, l, r))
}
BinaryOp::Div if left.ty == Type::U64 => {
self.lines.push(format!("{} = udiv i64 {}, {}", reg, l, r))
}
BinaryOp::Rem if left.ty == Type::I64 => {
self.lines.push(format!("{} = srem i64 {}, {}", reg, l, r))
}
BinaryOp::Rem if left.ty == Type::U64 => {
self.lines.push(format!("{} = urem i64 {}, {}", reg, l, r))
}
BinaryOp::BitAnd if left.ty == Type::I64 => {
self.lines.push(format!("{} = and i64 {}, {}", reg, l, r))
}
BinaryOp::BitAnd if left.ty == Type::U64 => {
self.lines.push(format!("{} = and i64 {}, {}", reg, l, r))
}
BinaryOp::BitOr if left.ty == Type::I64 => {
self.lines.push(format!("{} = or i64 {}, {}", reg, l, r))
}
BinaryOp::BitOr if left.ty == Type::U64 => {
self.lines.push(format!("{} = or i64 {}, {}", reg, l, r))
}
BinaryOp::BitXor if left.ty == Type::I64 => {
self.lines.push(format!("{} = xor i64 {}, {}", reg, l, r))
}
BinaryOp::BitXor if left.ty == Type::U64 => {
self.lines.push(format!("{} = xor i64 {}, {}", reg, l, r))
}
BinaryOp::Add => self.lines.push(format!("{} = add i32 {}, {}", reg, l, r)),
BinaryOp::Sub => self.lines.push(format!("{} = sub i32 {}, {}", reg, l, r)),
BinaryOp::Mul => self.lines.push(format!("{} = mul i32 {}, {}", reg, l, r)),
BinaryOp::Div if left.ty == Type::U32 => {
self.lines.push(format!("{} = udiv i32 {}, {}", reg, l, r))
}
BinaryOp::Div => self.lines.push(format!("{} = sdiv i32 {}, {}", reg, l, r)),
BinaryOp::Rem if left.ty == Type::U32 => {
self.lines.push(format!("{} = urem i32 {}, {}", reg, l, r))
}
BinaryOp::Rem => self.lines.push(format!("{} = srem i32 {}, {}", reg, l, r)),
BinaryOp::BitAnd => self.lines.push(format!("{} = and i32 {}, {}", reg, l, r)),
BinaryOp::BitOr => self.lines.push(format!("{} = or i32 {}, {}", reg, l, r)),
BinaryOp::BitXor => self.lines.push(format!("{} = xor i32 {}, {}", reg, l, r)),
BinaryOp::Eq if left.ty == Type::String && right.ty == Type::String => {
self.lines.push(format!(
"{} = call i1 @__glagol_string_eq(ptr {}, ptr {})",
reg, l, r
))
}
BinaryOp::Eq
if vec_eq_runtime_symbol(&left.ty).is_some() && left.ty == right.ty =>
{
let symbol =
vec_eq_runtime_symbol(&left.ty).expect("vector equality symbol");
self.lines.push(format!(
"{} = call i1 @{}(ptr {}, ptr {})",
reg, symbol, l, r
))
}
BinaryOp::Eq if self.is_payload_enum_type(&left.ty) => {
let left_ty = self.llvm_type(left, &left.ty)?;
let payload_ty = self
.enum_payload_type(&left.ty)
.ok_or_else(|| self.unsupported(expr, "enum payload metadata"))?
.clone();
self.emit_payload_enum_equality(&reg, &payload_ty, &left_ty, &l, &r)?;
}
BinaryOp::Eq if left.ty == Type::F64 => self
.lines
.push(format!("{} = fcmp oeq double {}, {}", reg, l, r)),
BinaryOp::Eq if left.ty == Type::Bool => self
.lines
.push(format!("{} = icmp eq i1 {}, {}", reg, l, r)),
BinaryOp::Eq if left.ty == Type::I64 => self
.lines
.push(format!("{} = icmp eq i64 {}, {}", reg, l, r)),
BinaryOp::Eq if left.ty == Type::U64 => self
.lines
.push(format!("{} = icmp eq i64 {}, {}", reg, l, r)),
BinaryOp::Eq => self
.lines
.push(format!("{} = icmp eq i32 {}, {}", reg, l, r)),
BinaryOp::Lt if left.ty == Type::F64 => self
.lines
.push(format!("{} = fcmp olt double {}, {}", reg, l, r)),
BinaryOp::Lt if left.ty == Type::I64 => self
.lines
.push(format!("{} = icmp slt i64 {}, {}", reg, l, r)),
BinaryOp::Lt if left.ty == Type::U64 => self
.lines
.push(format!("{} = icmp ult i64 {}, {}", reg, l, r)),
BinaryOp::Lt if left.ty == Type::U32 => self
.lines
.push(format!("{} = icmp ult i32 {}, {}", reg, l, r)),
BinaryOp::Lt => self
.lines
.push(format!("{} = icmp slt i32 {}, {}", reg, l, r)),
BinaryOp::Gt if left.ty == Type::F64 => self
.lines
.push(format!("{} = fcmp ogt double {}, {}", reg, l, r)),
BinaryOp::Gt if left.ty == Type::I64 => self
.lines
.push(format!("{} = icmp sgt i64 {}, {}", reg, l, r)),
BinaryOp::Gt if left.ty == Type::U64 => self
.lines
.push(format!("{} = icmp ugt i64 {}, {}", reg, l, r)),
BinaryOp::Gt if left.ty == Type::U32 => self
.lines
.push(format!("{} = icmp ugt i32 {}, {}", reg, l, r)),
BinaryOp::Gt => self
.lines
.push(format!("{} = icmp sgt i32 {}, {}", reg, l, r)),
BinaryOp::Le if left.ty == Type::F64 => self
.lines
.push(format!("{} = fcmp ole double {}, {}", reg, l, r)),
BinaryOp::Le if left.ty == Type::I64 => self
.lines
.push(format!("{} = icmp sle i64 {}, {}", reg, l, r)),
BinaryOp::Le if left.ty == Type::U64 => self
.lines
.push(format!("{} = icmp ule i64 {}, {}", reg, l, r)),
BinaryOp::Le if left.ty == Type::U32 => self
.lines
.push(format!("{} = icmp ule i32 {}, {}", reg, l, r)),
BinaryOp::Le => self
.lines
.push(format!("{} = icmp sle i32 {}, {}", reg, l, r)),
BinaryOp::Ge if left.ty == Type::F64 => self
.lines
.push(format!("{} = fcmp oge double {}, {}", reg, l, r)),
BinaryOp::Ge if left.ty == Type::I64 => self
.lines
.push(format!("{} = icmp sge i64 {}, {}", reg, l, r)),
BinaryOp::Ge if left.ty == Type::U64 => self
.lines
.push(format!("{} = icmp uge i64 {}, {}", reg, l, r)),
BinaryOp::Ge if left.ty == Type::U32 => self
.lines
.push(format!("{} = icmp uge i32 {}, {}", reg, l, r)),
BinaryOp::Ge => self
.lines
.push(format!("{} = icmp sge i32 {}, {}", reg, l, r)),
}
Ok(reg)
}
TExprKind::Call { name, args } => {
match name.as_str() {
"std.num.i32_to_i64" => {
return self
.emit_numeric_widening_conversion(expr, args, "sext", "i32", "i64");
}
"std.num.i32_to_f64" => {
return self.emit_numeric_widening_conversion(
expr, args, "sitofp", "i32", "double",
);
}
"std.num.i64_to_f64" => {
return self.emit_numeric_widening_conversion(
expr, args, "sitofp", "i64", "double",
);
}
"std.num.i64_to_i32_result" => {
return self.emit_i64_to_i32_result_conversion(expr, args);
}
"std.num.f64_to_i32_result" => {
return self.emit_f64_to_i32_result_conversion(expr, args);
}
"std.num.f64_to_i64_result" => {
return self.emit_f64_to_i64_result_conversion(expr, args);
}
_ => {}
}
let mut arg_values = Vec::new();
for arg in args {
let value = self.emit_expr(arg)?;
let arg_ty = self.llvm_type(arg, &arg.ty)?;
arg_values.push(format!("{} {}", arg_ty, value));
}
let arg_values = arg_values.join(", ");
let callee = std_runtime::runtime_symbol(name).unwrap_or(name);
if matches!(
callee,
"__glagol_process_arg_result"
| "__glagol_env_get_result"
| "__glagol_fs_read_text_result"
| "__glagol_fs_read_open_text_result"
| "__glagol_net_tcp_read_all_result"
| "__glagol_io_read_stdin_result"
| "__glagol_string_slice_result"
) {
return self.emit_string_result_host_call(expr, callee, &arg_values);
}
if callee == "__glagol_fs_write_text_result"
|| callee == "__glagol_fs_remove_file_result"
|| callee == "__glagol_fs_create_dir_result"
|| callee == "__glagol_fs_close_result"
|| callee == "__glagol_net_tcp_write_text_result"
|| callee == "__glagol_net_tcp_close_result"
{
return self.emit_i32_result_status_call(expr, callee, &arg_values);
}
if callee == "__glagol_fs_open_text_read_result"
|| callee == "__glagol_net_tcp_connect_loopback_result"
|| callee == "__glagol_net_tcp_listen_loopback_result"
|| callee == "__glagol_net_tcp_bound_port_result"
|| callee == "__glagol_net_tcp_accept_result"
|| callee == "__glagol_string_parse_i32_result"
|| callee == "__glagol_json_parse_i32_value_result"
|| callee == "__glagol_string_byte_at_result"
{
return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values);
}
if callee == "__glagol_string_parse_u32_result"
|| callee == "__glagol_json_parse_u32_value_result"
{
return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values);
}
if callee == "__glagol_string_parse_i64_result"
|| callee == "__glagol_json_parse_i64_value_result"
{
return self.emit_i64_result_out_param_call(expr, callee, &arg_values);
}
if callee == "__glagol_string_parse_u64_result"
|| callee == "__glagol_json_parse_u64_value_result"
{
return self.emit_i64_result_out_param_call(expr, callee, &arg_values);
}
if callee == "__glagol_string_parse_f64_result"
|| callee == "__glagol_json_parse_f64_value_result"
{
return self.emit_f64_result_out_param_call(expr, callee, &arg_values);
}
if callee == "__glagol_string_parse_bool_result"
|| callee == "__glagol_json_parse_bool_value_result"
{
return self.emit_bool_result_out_param_call(expr, callee, &arg_values);
}
if expr.ty == Type::Unit {
self.lines
.push(format!("call void @{}({})", callee, arg_values));
Ok("0".to_string())
} else {
let reg = self.reg();
let result_ty = self.llvm_type(expr, &expr.ty)?;
self.lines.push(format!(
"{} = call {} @{}({})",
reg, result_ty, callee, arg_values
));
Ok(reg)
}
}
TExprKind::If {
condition,
then_expr,
else_expr,
} => {
let then_block = self.block("if.then");
let else_block = self.block("if.else");
let end_block = self.block("if.end");
let cond = self.emit_expr(condition)?;
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
cond, then_block, else_block
));
self.label(&then_block);
let then_value = self.emit_expr(then_expr)?;
let then_pred = self.current_block.clone();
self.lines.push(format!("br label %{}", end_block));
self.label(&else_block);
let else_value = self.emit_expr(else_expr)?;
let else_pred = self.current_block.clone();
self.lines.push(format!("br label %{}", end_block));
self.label(&end_block);
if expr.ty == Type::Unit {
Ok("0".to_string())
} else {
let reg = self.reg();
let phi_ty = self.llvm_type(expr, &expr.ty)?;
self.lines.push(format!(
"{} = phi {} [ {}, %{} ], [ {}, %{} ]",
reg, phi_ty, then_value, then_pred, else_value, else_pred,
));
Ok(reg)
}
}
TExprKind::Match { subject, arms } => self.emit_match(expr, subject, arms),
TExprKind::While { condition, body } => {
let cond_block = self.block("while.cond");
let body_block = self.block("while.body");
let end_block = self.block("while.end");
self.lines.push(format!("br label %{}", cond_block));
self.label(&cond_block);
let cond = self.emit_expr(condition)?;
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
cond, body_block, end_block
));
self.label(&body_block);
for expr in body {
self.emit_expr(expr)?;
}
self.lines.push(format!("br label %{}", cond_block));
self.label(&end_block);
Ok("0".to_string())
}
TExprKind::Unsafe { body } => {
let saved_locals = self.locals.clone();
let mut value = "0".to_string();
for expr in body {
value = self.emit_expr(expr)?;
}
self.locals = saved_locals;
Ok(value)
}
}
}
fn emit_option_aggregate(
&mut self,
expr: &TExpr,
tag: bool,
payload: Option<&TExpr>,
) -> Result<String, Diagnostic> {
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let tag_value = if tag { "1" } else { "0" };
let tagged = self.reg();
self.lines.push(format!(
"{} = insertvalue {} undef, i1 {}, 0",
tagged, aggregate_ty, tag_value
));
let payload_value = match payload {
Some(payload) => self.emit_expr(payload)?,
None => match &expr.ty {
Type::Option(inner) => zero_value_for_type(inner).ok_or_else(|| {
self.unsupported(expr, "option constructor for unsupported payload type")
})?,
_ => return Err(self.unsupported(expr, "option constructor for non-option type")),
},
};
let payload_ty = match &expr.ty {
Type::Option(inner) if **inner == Type::I32 => "i32",
Type::Option(inner) if **inner == Type::I64 => "i64",
Type::Option(inner) if **inner == Type::U32 => "i32",
Type::Option(inner) if **inner == Type::U64 => "i64",
Type::Option(inner) if **inner == Type::F64 => "double",
Type::Option(inner) if **inner == Type::Bool => "i1",
Type::Option(inner) if **inner == Type::String => "ptr",
_ => return Err(self.unsupported(expr, "option constructor for non-option type")),
};
let with_payload = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, {} {}, 1",
with_payload, aggregate_ty, tagged, payload_ty, payload_value
));
Ok(with_payload)
}
fn emit_tagged_i32_aggregate(
&mut self,
expr: &TExpr,
tag: bool,
payload: Option<&TExpr>,
) -> Result<String, Diagnostic> {
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let tag_value = if tag { "1" } else { "0" };
let tagged = self.reg();
self.lines.push(format!(
"{} = insertvalue {} undef, i1 {}, 0",
tagged, aggregate_ty, tag_value
));
let payload_value = match payload {
Some(payload) => self.emit_expr(payload)?,
None => "0".to_string(),
};
let with_payload = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, i32 {}, 1",
with_payload, aggregate_ty, tagged, payload_value
));
Ok(with_payload)
}
fn emit_enum_variant(
&mut self,
expr: &TExpr,
enum_name: &str,
discriminant: i32,
payload: Option<&TExpr>,
) -> Result<String, Diagnostic> {
let Some(layout) = self.enum_layouts.get(enum_name) else {
return Ok(discriminant.to_string());
};
let Some(payload_ty) = layout.payload_ty.as_ref() else {
return Ok(discriminant.to_string());
};
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let tagged = self.reg();
self.lines.push(format!(
"{} = insertvalue {} undef, i32 {}, 0",
tagged, aggregate_ty, discriminant
));
let payload_value = match payload {
Some(payload) => self.emit_expr(payload)?,
None => self.enum_payload_zero_value(payload_ty)?,
};
let payload_llvm_ty = self.enum_payload_llvm_type(payload_ty)?;
let with_payload = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, {} {}, 1",
with_payload, aggregate_ty, tagged, payload_llvm_ty, payload_value
));
Ok(with_payload)
}
fn emit_result_aggregate(
&mut self,
expr: &TExpr,
is_ok: bool,
payload: &TExpr,
) -> Result<String, Diagnostic> {
match &expr.ty {
Type::Result(ok, err) if **ok == Type::I32 && **err == Type::I32 => {
self.emit_tagged_i32_aggregate(expr, is_ok, Some(payload))
}
Type::Result(ok, err) if **ok == Type::I64 && **err == Type::I32 => {
let payload_value = self.emit_expr(payload)?;
if is_ok {
self.emit_i64_result_aggregate(expr, "1", &payload_value, "0")
} else {
self.emit_i64_result_aggregate(expr, "0", "0", &payload_value)
}
}
Type::Result(ok, err) if **ok == Type::U32 && **err == Type::I32 => {
self.emit_tagged_i32_aggregate(expr, is_ok, Some(payload))
}
Type::Result(ok, err) if **ok == Type::U64 && **err == Type::I32 => {
let payload_value = self.emit_expr(payload)?;
if is_ok {
self.emit_i64_result_aggregate(expr, "1", &payload_value, "0")
} else {
self.emit_i64_result_aggregate(expr, "0", "0", &payload_value)
}
}
Type::Result(ok, err) if **ok == Type::F64 && **err == Type::I32 => {
let payload_value = self.emit_expr(payload)?;
if is_ok {
self.emit_f64_result_aggregate(expr, "1", &payload_value, "0")
} else {
self.emit_f64_result_aggregate(expr, "0", "0.0", &payload_value)
}
}
Type::Result(ok, err) if **ok == Type::Bool && **err == Type::I32 => {
let payload_value = self.emit_expr(payload)?;
if is_ok {
self.emit_bool_result_aggregate(expr, "1", &payload_value, "0")
} else {
self.emit_bool_result_aggregate(expr, "0", "0", &payload_value)
}
}
Type::Result(ok, err) if **ok == Type::String && **err == Type::I32 => {
let payload_value = self.emit_expr(payload)?;
if is_ok {
self.emit_string_result_aggregate(expr, "1", &payload_value, "0")
} else {
self.emit_string_result_aggregate(expr, "0", "null", &payload_value)
}
}
Type::Result(_, _) => Err(self.unsupported(expr, "unsupported result payload types")),
_ => Err(self.unsupported(expr, "result constructor for non-result type")),
}
}
fn emit_string_result_aggregate(
&mut self,
expr: &TExpr,
tag_value: &str,
ok_value: &str,
err_value: &str,
) -> Result<String, Diagnostic> {
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let tagged = self.reg();
self.lines.push(format!(
"{} = insertvalue {} undef, i1 {}, 0",
tagged, aggregate_ty, tag_value
));
let with_ok = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, ptr {}, 1",
with_ok, aggregate_ty, tagged, ok_value
));
let with_err = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, i32 {}, 2",
with_err, aggregate_ty, with_ok, err_value
));
Ok(with_err)
}
fn emit_i64_result_aggregate(
&mut self,
expr: &TExpr,
tag_value: &str,
ok_value: &str,
err_value: &str,
) -> Result<String, Diagnostic> {
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let tagged = self.reg();
self.lines.push(format!(
"{} = insertvalue {} undef, i1 {}, 0",
tagged, aggregate_ty, tag_value
));
let with_ok = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, i64 {}, 1",
with_ok, aggregate_ty, tagged, ok_value
));
let with_err = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, i32 {}, 2",
with_err, aggregate_ty, with_ok, err_value
));
Ok(with_err)
}
fn emit_f64_result_aggregate(
&mut self,
expr: &TExpr,
tag_value: &str,
ok_value: &str,
err_value: &str,
) -> Result<String, Diagnostic> {
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let tagged = self.reg();
self.lines.push(format!(
"{} = insertvalue {} undef, i1 {}, 0",
tagged, aggregate_ty, tag_value
));
let with_ok = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, double {}, 1",
with_ok, aggregate_ty, tagged, ok_value
));
let with_err = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, i32 {}, 2",
with_err, aggregate_ty, with_ok, err_value
));
Ok(with_err)
}
fn emit_bool_result_aggregate(
&mut self,
expr: &TExpr,
tag_value: &str,
ok_value: &str,
err_value: &str,
) -> Result<String, Diagnostic> {
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let tagged = self.reg();
self.lines.push(format!(
"{} = insertvalue {} undef, i1 {}, 0",
tagged, aggregate_ty, tag_value
));
let with_ok = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, i1 {}, 1",
with_ok, aggregate_ty, tagged, ok_value
));
let with_err = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, i32 {}, 2",
with_err, aggregate_ty, with_ok, err_value
));
Ok(with_err)
}
fn emit_string_result_host_call(
&mut self,
expr: &TExpr,
callee: &str,
arg_values: &str,
) -> Result<String, Diagnostic> {
let payload = self.reg();
self.lines.push(format!(
"{} = call ptr @{}({})",
payload, callee, arg_values
));
let is_ok = self.reg();
self.lines
.push(format!("{} = icmp ne ptr {}, null", is_ok, payload));
let err_code = self.reg();
self.lines
.push(format!("{} = select i1 {}, i32 0, i32 1", err_code, is_ok));
self.emit_string_result_aggregate(expr, &is_ok, &payload, &err_code)
}
fn emit_i32_result_status_call(
&mut self,
expr: &TExpr,
callee: &str,
arg_values: &str,
) -> Result<String, Diagnostic> {
let status = self.reg();
self.lines
.push(format!("{} = call i32 @{}({})", status, callee, arg_values));
let is_ok = self.reg();
self.lines
.push(format!("{} = icmp eq i32 {}, 0", is_ok, status));
self.emit_tagged_i32_aggregate_from_values(expr, &is_ok, &status)
}
fn emit_i32_result_encoded_i64_call(
&mut self,
expr: &TExpr,
callee: &str,
arg_values: &str,
) -> Result<String, Diagnostic> {
let encoded = self.reg();
self.lines.push(format!(
"{} = call i64 @{}({})",
encoded, callee, arg_values
));
let status64 = self.reg();
self.lines
.push(format!("{} = lshr i64 {}, 32", status64, encoded));
let status = self.reg();
self.lines
.push(format!("{} = trunc i64 {} to i32", status, status64));
let is_ok = self.reg();
self.lines
.push(format!("{} = icmp eq i32 {}, 0", is_ok, status));
let payload = self.reg();
self.lines
.push(format!("{} = trunc i64 {} to i32", payload, encoded));
self.emit_tagged_i32_aggregate_from_values(expr, &is_ok, &payload)
}
fn emit_i64_result_out_param_call(
&mut self,
expr: &TExpr,
callee: &str,
arg_values: &str,
) -> Result<String, Diagnostic> {
let out_ptr = self.reg();
self.lines.push(format!("{} = alloca i64", out_ptr));
self.lines.push(format!("store i64 0, ptr {}", out_ptr));
let status = self.reg();
self.lines.push(format!(
"{} = call i32 @{}({}, ptr {})",
status, callee, arg_values, out_ptr
));
let is_ok = self.reg();
self.lines
.push(format!("{} = icmp eq i32 {}, 0", is_ok, status));
let ok_payload = self.reg();
self.lines
.push(format!("{} = load i64, ptr {}", ok_payload, out_ptr));
let err_payload = self.reg();
self.lines.push(format!(
"{} = select i1 {}, i32 0, i32 1",
err_payload, is_ok
));
self.emit_i64_result_aggregate(expr, &is_ok, &ok_payload, &err_payload)
}
fn emit_f64_result_out_param_call(
&mut self,
expr: &TExpr,
callee: &str,
arg_values: &str,
) -> Result<String, Diagnostic> {
let out_ptr = self.reg();
self.lines.push(format!("{} = alloca double", out_ptr));
self.lines
.push(format!("store double 0.0, ptr {}", out_ptr));
let status = self.reg();
self.lines.push(format!(
"{} = call i32 @{}({}, ptr {})",
status, callee, arg_values, out_ptr
));
let is_ok = self.reg();
self.lines
.push(format!("{} = icmp eq i32 {}, 0", is_ok, status));
let ok_payload = self.reg();
self.lines
.push(format!("{} = load double, ptr {}", ok_payload, out_ptr));
let err_payload = self.reg();
self.lines.push(format!(
"{} = select i1 {}, i32 0, i32 1",
err_payload, is_ok
));
self.emit_f64_result_aggregate(expr, &is_ok, &ok_payload, &err_payload)
}
fn emit_bool_result_out_param_call(
&mut self,
expr: &TExpr,
callee: &str,
arg_values: &str,
) -> Result<String, Diagnostic> {
let out_ptr = self.reg();
self.lines.push(format!("{} = alloca i1", out_ptr));
self.lines.push(format!("store i1 0, ptr {}", out_ptr));
let status = self.reg();
self.lines.push(format!(
"{} = call i32 @{}({}, ptr {})",
status, callee, arg_values, out_ptr
));
let is_ok = self.reg();
self.lines
.push(format!("{} = icmp eq i32 {}, 0", is_ok, status));
let ok_payload = self.reg();
self.lines
.push(format!("{} = load i1, ptr {}", ok_payload, out_ptr));
let err_payload = self.reg();
self.lines.push(format!(
"{} = select i1 {}, i32 0, i32 1",
err_payload, is_ok
));
self.emit_bool_result_aggregate(expr, &is_ok, &ok_payload, &err_payload)
}
fn emit_tagged_i32_aggregate_from_values(
&mut self,
expr: &TExpr,
tag_value: &str,
payload_value: &str,
) -> Result<String, Diagnostic> {
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let tagged = self.reg();
self.lines.push(format!(
"{} = insertvalue {} undef, i1 {}, 0",
tagged, aggregate_ty, tag_value
));
let with_payload = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, i32 {}, 1",
with_payload, aggregate_ty, tagged, payload_value
));
Ok(with_payload)
}
fn emit_tag_observer(
&mut self,
_expr: &TExpr,
value: &TExpr,
invert: bool,
) -> Result<String, Diagnostic> {
let aggregate = self.emit_expr(value)?;
let aggregate_ty = self.llvm_type(value, &value.ty)?;
let tag = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, 0",
tag, aggregate_ty, aggregate
));
if invert {
let inverted = self.reg();
self.lines
.push(format!("{} = xor i1 {}, true", inverted, tag));
Ok(inverted)
} else {
Ok(tag)
}
}
fn emit_tag_checked_payload_access(
&mut self,
_expr: &TExpr,
value: &TExpr,
expected_tag: bool,
trap_fn: &str,
) -> Result<String, Diagnostic> {
let aggregate = self.emit_expr(value)?;
let aggregate_ty = self.llvm_type(value, &value.ty)?;
let tag = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, 0",
tag, aggregate_ty, aggregate
));
let valid = if expected_tag {
tag
} else {
let valid = self.reg();
self.lines
.push(format!("{} = icmp eq i1 {}, 0", valid, tag));
valid
};
let ok_block = self.block("unwrap.ok");
let trap_block = self.block("unwrap.trap");
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
valid, ok_block, trap_block
));
self.label(&trap_block);
self.lines.push(format!("call void @{}()", trap_fn));
self.lines.push("unreachable".to_string());
self.label(&ok_block);
let payload_index = result_payload_index(&value.ty, expected_tag)
.ok_or_else(|| self.unsupported(value, "unsupported result payload access"))?;
let payload = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, {}",
payload, aggregate_ty, aggregate, payload_index
));
Ok(payload)
}
fn emit_match(
&mut self,
expr: &TExpr,
subject: &TExpr,
arms: &[TMatchArm],
) -> Result<String, Diagnostic> {
if matches!(subject.ty, Type::Named(_)) {
return self.emit_enum_match(expr, subject, arms);
}
let subject_value = self.emit_expr(subject)?;
let aggregate_ty = self.llvm_type(subject, &subject.ty)?;
let tag = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, 0",
tag, aggregate_ty, subject_value
));
let (truthy_kind, falsy_kind, truthy_prefix, falsy_prefix) = match &subject.ty {
Type::Option(inner)
if **inner == Type::I32
|| **inner == Type::I64
|| **inner == Type::U32
|| **inner == Type::U64
|| **inner == Type::F64
|| **inner == Type::Bool
|| **inner == Type::String =>
{
(
MatchPatternKind::Some,
MatchPatternKind::None,
"match.some",
"match.none",
)
}
Type::Result(ok, err) if result_type_supported(ok, err) => (
MatchPatternKind::Ok,
MatchPatternKind::Err,
"match.ok",
"match.err",
),
_ => {
return Err(self.unsupported(
expr,
"match subject types outside concrete option/result families",
))
}
};
let truthy_arm = arms
.iter()
.find(|arm| arm.pattern == truthy_kind)
.ok_or_else(|| self.unsupported(expr, "non-exhaustive match lowering"))?;
let falsy_arm = arms
.iter()
.find(|arm| arm.pattern == falsy_kind)
.ok_or_else(|| self.unsupported(expr, "non-exhaustive match lowering"))?;
let truthy_block = self.block(truthy_prefix);
let falsy_block = self.block(falsy_prefix);
let end_block = self.block("match.end");
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
tag, truthy_block, falsy_block
));
self.label(&truthy_block);
let truthy_value =
self.emit_match_arm(truthy_arm, &subject.ty, &aggregate_ty, &subject_value)?;
let truthy_pred = self.current_block.clone();
self.lines.push(format!("br label %{}", end_block));
self.label(&falsy_block);
let falsy_value =
self.emit_match_arm(falsy_arm, &subject.ty, &aggregate_ty, &subject_value)?;
let falsy_pred = self.current_block.clone();
self.lines.push(format!("br label %{}", end_block));
self.label(&end_block);
if expr.ty == Type::Unit {
Ok("0".to_string())
} else {
let reg = self.reg();
let phi_ty = self.llvm_type(expr, &expr.ty)?;
self.lines.push(format!(
"{} = phi {} [ {}, %{} ], [ {}, %{} ]",
reg, phi_ty, truthy_value, truthy_pred, falsy_value, falsy_pred,
));
Ok(reg)
}
}
fn emit_enum_match(
&mut self,
expr: &TExpr,
subject: &TExpr,
arms: &[TMatchArm],
) -> Result<String, Diagnostic> {
let subject_value = self.emit_expr(subject)?;
let subject_llvm_ty = self.llvm_type(subject, &subject.ty)?;
let switch_value = if self.is_payload_enum_type(&subject.ty) {
let tag = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, 0",
tag, subject_llvm_ty, subject_value
));
tag
} else {
subject_value.clone()
};
let end_block = self.block("match.end");
let default_block = self.block("match.invalid");
let mut arm_blocks = Vec::new();
for arm in arms {
let Some(discriminant) = arm.discriminant else {
return Err(self.unsupported(expr, "enum match arm without discriminant"));
};
arm_blocks.push((discriminant, self.block("match.enum"), arm));
}
let cases = arm_blocks
.iter()
.map(|(discriminant, block, _)| format!("i32 {}, label %{}", discriminant, block))
.collect::<Vec<_>>()
.join("\n ");
self.lines.push(format!(
"switch i32 {}, label %{} [\n {}\n ]",
switch_value, default_block, cases
));
self.label(&default_block);
self.lines.push("unreachable".to_string());
let mut values = Vec::new();
for (_, block, arm) in arm_blocks {
self.label(&block);
let value = self.emit_match_arm(arm, &subject.ty, &subject_llvm_ty, &subject_value)?;
let pred = self.current_block.clone();
self.lines.push(format!("br label %{}", end_block));
values.push((value, pred));
}
self.label(&end_block);
if expr.ty == Type::Unit {
Ok("0".to_string())
} else {
let reg = self.reg();
let phi_ty = self.llvm_type(expr, &expr.ty)?;
let incoming = values
.into_iter()
.map(|(value, pred)| format!("[ {}, %{} ]", value, pred))
.collect::<Vec<_>>()
.join(", ");
self.lines
.push(format!("{} = phi {} {}", reg, phi_ty, incoming));
Ok(reg)
}
}
fn emit_match_arm(
&mut self,
arm: &TMatchArm,
subject_ty: &Type,
aggregate_ty: &str,
subject_value: &str,
) -> Result<String, Diagnostic> {
let saved_locals = self.locals.clone();
if let Some(binding) = &arm.binding {
let payload_index = match subject_ty {
Type::Named(name)
if self
.enum_layouts
.get(name)
.and_then(|layout| layout.payload_ty.as_ref())
.is_some() =>
{
Some(1)
}
Type::Option(_) => Some(1),
Type::Result(_, _) => {
result_payload_index(subject_ty, matches!(arm.pattern, MatchPatternKind::Ok))
}
_ => None,
}
.ok_or_else(|| {
Diagnostic::new(
&self.file,
"UnsupportedBackendFeature",
"backend does not support this match payload binding",
)
})?;
let payload = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, {}",
payload, aggregate_ty, subject_value, payload_index
));
self.locals
.insert(binding.clone(), LocalValue::Value(payload));
}
let mut value = "0".to_string();
for expr in &arm.body {
value = self.emit_expr(expr)?;
}
self.locals = saved_locals;
Ok(value)
}
fn emit_struct_init(
&mut self,
expr: &TExpr,
_name: &str,
fields: &[(String, TExpr)],
) -> Result<String, Diagnostic> {
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let mut aggregate = "undef".to_string();
for (index, (_, field_expr)) in fields.iter().enumerate() {
let value = self.emit_expr(field_expr)?;
let field_ty = self.llvm_type(field_expr, &field_expr.ty)?;
let with_field = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, {} {}, {}",
with_field, aggregate_ty, aggregate, field_ty, value, index
));
aggregate = with_field;
}
Ok(aggregate)
}
fn emit_array_init(&mut self, expr: &TExpr, elements: &[TExpr]) -> Result<String, Diagnostic> {
let aggregate_ty = self.llvm_type(expr, &expr.ty)?;
let mut aggregate = "undef".to_string();
for (index, element) in elements.iter().enumerate() {
let value = self.emit_expr(element)?;
let element_ty = self.llvm_type(element, &element.ty)?;
let with_element = self.reg();
self.lines.push(format!(
"{} = insertvalue {} {}, {} {}, {}",
with_element, aggregate_ty, aggregate, element_ty, value, index
));
aggregate = with_element;
}
Ok(aggregate)
}
fn emit_ssa_local(&mut self, name: &str, initializer: &TExpr) -> Result<String, Diagnostic> {
let value = self.emit_expr(initializer)?;
self.locals
.insert(name.to_string(), LocalValue::Value(value));
Ok("0".to_string())
}
fn emit_value_local(&mut self, name: &str, initializer: &TExpr) -> Result<String, Diagnostic> {
let value = self.emit_expr(initializer)?;
let llvm_ty = self.llvm_type(initializer, &initializer.ty)?;
let ptr = self.local_addr(name);
self.lines.push(format!("{} = alloca {}", ptr, llvm_ty));
self.lines
.push(format!("store {} {}, ptr {}", llvm_ty, value, ptr));
self.locals.insert(
name.to_string(),
LocalValue::Ptr {
ptr,
ty: initializer.ty.clone(),
},
);
Ok("0".to_string())
}
fn emit_array_local(
&mut self,
_expr: &TExpr,
name: &str,
initializer: &TExpr,
) -> Result<String, Diagnostic> {
let Type::Array(inner, len) = &initializer.ty else {
return Err(self.unsupported(initializer, "unsupported array locals"));
};
let llvm_ty = llvm_fixed_array_type(self.layouts, self.enum_layouts, inner, *len)
.ok_or_else(|| self.unsupported(initializer, "unsupported array locals"))?;
let ptr = self.local_addr(name);
self.lines.push(format!("{} = alloca {}", ptr, llvm_ty));
if let TExprKind::ArrayInit { elements } = &initializer.kind {
for (index, element) in elements.iter().enumerate() {
let value = self.emit_expr(element)?;
let element_ty = self.llvm_type(element, &element.ty)?;
let element_ptr = self.reg();
self.lines.push(format!(
"{} = getelementptr inbounds {}, ptr {}, i32 0, i32 {}",
element_ptr, llvm_ty, ptr, index
));
self.lines.push(format!(
"store {} {}, ptr {}",
element_ty, value, element_ptr
));
}
} else {
let value = self.emit_expr(initializer)?;
self.lines
.push(format!("store {} {}, ptr {}", llvm_ty, value, ptr));
}
self.locals.insert(
name.to_string(),
LocalValue::Array {
ptr,
ty: initializer.ty.clone(),
},
);
Ok("0".to_string())
}
fn emit_index(
&mut self,
expr: &TExpr,
array: &TExpr,
index: &TExpr,
) -> Result<String, Diagnostic> {
if let (TExprKind::ArrayInit { elements }, TExprKind::Int(index)) =
(&array.kind, &index.kind)
{
let index = usize::try_from(*index)
.map_err(|_| self.unsupported(expr, "negative array indices"))?;
if index >= elements.len() {
return Err(self.unsupported(expr, "out-of-bounds array indices"));
}
let aggregate = self.emit_expr(array)?;
let aggregate_ty = self.llvm_type(array, &array.ty)?;
let value = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, {}",
value, aggregate_ty, aggregate, index
));
return Ok(value);
}
let Type::Array(inner, len) = &array.ty else {
return Err(self.unsupported(expr, "indexing non-array values"));
};
let llvm_array_ty = llvm_fixed_array_type(self.layouts, self.enum_layouts, inner, *len)
.ok_or_else(|| self.unsupported(expr, "unsupported fixed array indexing"))?;
let element_ty = self.llvm_type(expr, inner)?;
let (array_ptr, len) = self.emit_array_address(expr, array)?;
let index_value = self.emit_expr(index)?;
self.emit_array_bounds_check(&index_value, len);
let element_ptr = self.reg();
self.lines.push(format!(
"{} = getelementptr inbounds {}, ptr {}, i32 0, i32 {}",
element_ptr, llvm_array_ty, array_ptr, index_value
));
let value = self.reg();
self.lines.push(format!(
"{} = load {}, ptr {}",
value, element_ty, element_ptr
));
Ok(value)
}
fn emit_array_address(
&mut self,
_expr: &TExpr,
array: &TExpr,
) -> Result<(String, usize), Diagnostic> {
if let TExprKind::Var(name) = &array.kind {
if let Some(LocalValue::Array { ptr, ty }) = self.locals.get(name).cloned() {
let Type::Array(_, len) = ty else {
return Err(self.unsupported(array, "unsupported fixed array locals"));
};
return Ok((ptr, len));
}
}
let Type::Array(inner, len) = &array.ty else {
return Err(self.unsupported(array, "unsupported fixed array values"));
};
let value = self.emit_expr(array)?;
let llvm_ty = llvm_fixed_array_type(self.layouts, self.enum_layouts, inner, *len)
.ok_or_else(|| self.unsupported(array, "unsupported fixed array values"))?;
let ptr = self.local_addr("array.tmp");
self.lines.push(format!("{} = alloca {}", ptr, llvm_ty));
self.lines
.push(format!("store {} {}, ptr {}", llvm_ty, value, ptr));
Ok((ptr, *len))
}
fn emit_array_bounds_check(&mut self, index_value: &str, len: usize) {
let lower_ok = self.reg();
self.lines
.push(format!("{} = icmp sge i32 {}, 0", lower_ok, index_value));
let upper_ok = self.reg();
self.lines.push(format!(
"{} = icmp slt i32 {}, {}",
upper_ok, index_value, len
));
let in_bounds = self.reg();
self.lines
.push(format!("{} = and i1 {}, {}", in_bounds, lower_ok, upper_ok));
let ok_block = self.block("array.index.ok");
let trap_block = self.block("array.index.trap");
self.lines.push(format!(
"br i1 {}, label %{}, label %{}",
in_bounds, ok_block, trap_block
));
self.label(&trap_block);
self.lines
.push("call void @__glagol_array_bounds_trap()".to_string());
self.lines.push("unreachable".to_string());
self.label(&ok_block);
}
fn emit_field_access(
&mut self,
expr: &TExpr,
value: &TExpr,
field: &str,
) -> Result<String, Diagnostic> {
let Type::Named(struct_name) = &value.ty else {
return Err(self.unsupported(expr, "field access on non-struct values"));
};
let Some(layout) = self.layouts.get(struct_name) else {
return Err(self.unsupported(expr, "unknown struct layouts"));
};
let Some((index, (_, field_ty))) = layout
.fields
.iter()
.enumerate()
.find(|(_, (name, _))| name == field)
else {
return Err(self.unsupported(expr, "unknown struct fields"));
};
let aggregate = self.emit_expr(value)?;
let aggregate_ty = self.llvm_type(value, &value.ty)?;
let _field_ty = self.llvm_type(expr, field_ty)?;
let result = self.reg();
self.lines.push(format!(
"{} = extractvalue {} {}, {}",
result, aggregate_ty, aggregate, index
));
Ok(result)
}
}