2800 lines
103 KiB
Rust
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(®, &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)
|
|
}
|
|
}
|