Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ cargo run -- dump mir examples/basic.purs
`build <file.purs>...` resolves and links every listed module with the standard
library from `stdlib/lib` and writes the artifact. `wat <file.purs>...` renders
the text form. `dump <core|cc|mir> <file.purs>` prints an intermediate IR for
debugging. `main` must be a zero-argument `Int` declaration; its value becomes
the process exit code.
debugging. A selected `main :: Int` returns its value as the process exit code.
A selected `main :: Effect Unit` runs that action once and returns 0; a trap
still propagates.

For a guided, interactive walkthrough of P0 through P11, use the React
application in [`psrs-explorer/`](psrs-explorer/). It labels compact teaching
Expand All @@ -45,11 +46,11 @@ what remains in each layer.
| Gate | Measured | Scope |
| --- | --- | --- |
| L0/L1 lexing, layout, parsing | 904/908 | non-FFI `layout`, `passing`, `failing`, `warning` files; the four differences are recorded DEC-16 intentional differences |
| L2 resolution | 54/70 failing, 52/413 passing | official `errorCode`s; 106 `passing` files stop in surface lowering |
| L3 kinds | 27/48 failing | official kind `errorCode`s |
| L4 types | 12/40 failing | official `errorCode`s; most mismatches blocked on a missing library module |
| L5 classes | 41/81 failing | official `errorCode`s; most mismatches blocked on a missing library module |
| L6/M7 runtime | 0/413 passing | 244 blocked on a missing module, 106 in surface lowering |
| L2 resolution | 72/72 failing, 270/413 passing | official `errorCode`s; 53 `passing` files stop on a missing module and 86 at P3 |
| L3 kinds | 35/48 failing | official kind `errorCode`s |
| L4 types | 35/50 failing | official `errorCode`s |
| L5 classes | 53/80 failing | official `errorCode`s |
| L6/M7 runtime | 124/413 passing | all 124 exit 0; 63 have no selected `main`; 53 stop on a missing module |
| M8 warnings, optimization | not measured | no scoreboard exists |

Run the scoreboards yourself:
Expand Down
17 changes: 12 additions & 5 deletions crates/psrs-backend/src/abi/link/mod.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
//! Target-aware linking helpers for source WIT bindings.
//!
//! A foreign import's resolved source type is interned into the Core type table
//! so the backend can refer to it by identity. A structurally equal Core type
//! already in the table is reused, so a foreign signature shares the canonical
//! representation of the same type used elsewhere in the module. This replaces
//! recovering the type by structural search at the CC boundary.
//! WIT declarations consume the checked external schemes carried by Core.
//! Structural HIR interning remains available only to isolated ABI fixtures;
//! production linking never reconstructs the checked source type from HIR.

#[cfg(test)]
use psrs_core::{Module as CoreModule, Type as CoreType, TypeConstructor, TypeId as CoreTypeId};
#[cfg(test)]
use psrs_hir::{BuiltinType, Type as HirType, TypeKind as HirTypeKind};

/// Interns the resolved source type of a foreign import and returns its
/// [`CoreTypeId`]. The type is appended to the module type table when no
/// structurally equal type is present.
#[cfg(test)]
pub(crate) fn intern_source_type(module: &mut CoreModule, ty: &HirType) -> Option<CoreTypeId> {
intern_into(&mut module.types, ty)
}

#[cfg(test)]
fn intern_into(types: &mut Vec<CoreType>, ty: &HirType) -> Option<CoreTypeId> {
let core = match &ty.kind {
HirTypeKind::Constructor(BuiltinType::Int) => {
Expand Down Expand Up @@ -91,13 +93,15 @@ fn intern_into(types: &mut Vec<CoreType>, ty: &HirType) -> Option<CoreTypeId> {
Some(intern_core_type(types, core))
}

#[cfg(test)]
fn is_array_constructor(types: &[CoreType], id: CoreTypeId) -> bool {
matches!(
types.get(id.0 as usize),
Some(CoreType::Constructor(TypeConstructor::Array))
)
}

#[cfg(test)]
fn is_array_element(types: &[CoreType], id: CoreTypeId) -> bool {
match types.get(id.0 as usize) {
// `Unit` is a primitive but has no canonical list element.
Expand All @@ -122,6 +126,7 @@ fn is_array_element(types: &[CoreType], id: CoreTypeId) -> bool {
}
}

#[cfg(test)]
fn is_record(types: &[CoreType], id: CoreTypeId) -> bool {
let Some(CoreType::Application(function, _)) = types.get(id.0 as usize) else {
return false;
Expand All @@ -132,13 +137,15 @@ fn is_record(types: &[CoreType], id: CoreTypeId) -> bool {
)
}

#[cfg(test)]
fn is_user_type(types: &[CoreType], id: CoreTypeId) -> bool {
matches!(
types.get(id.0 as usize),
Some(CoreType::Constructor(TypeConstructor::User(_)))
)
}

#[cfg(test)]
fn intern_core_type(types: &mut Vec<CoreType>, core: CoreType) -> CoreTypeId {
if let Some(index) = types.iter().position(|existing| *existing == core) {
return CoreTypeId(index as u32);
Expand Down
1 change: 1 addition & 0 deletions crates/psrs-backend/src/abi/link/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fn module() -> CoreModule {
id: ModuleId(0),
name: "Main".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
CoreType::Constructor(psrs_core::TypeConstructor::Int),
CoreType::Constructor(psrs_core::TypeConstructor::Number),
Expand Down
1 change: 1 addition & 0 deletions crates/psrs-backend/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use canonical::{
resolve as resolve_canonical,
};
pub use handles::{HandleMode, HandleResource};
#[cfg(test)]
pub(crate) use link::intern_source_type;
use validation::{unsupported_shape, wasi_interface_enabled};

Expand Down
1 change: 1 addition & 0 deletions crates/psrs-backend/src/abi/tests/lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ fn empty_core() -> CoreModule {
id: ModuleId(0),
name: "Main".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: Vec::new(),
newtype_ids: Vec::new(),
opaque_ids: Vec::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/psrs-backend/src/abi/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ fn empty_core_module() -> psrs_core::Module {
id: psrs_hir::ModuleId(0),
name: "Main".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: Vec::new(),
newtype_ids: Vec::new(),
opaque_ids: Vec::new(),
Expand Down
61 changes: 28 additions & 33 deletions crates/psrs-backend/src/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::BackendError;
use crate::cc;
use psrs_core::{Module as CoreModule, TypeId as CoreTypeId};
use psrs_hir::{ExternalKind, SymbolId, Type as HirType};
use psrs_hir::{ExternalKind, ModuleId, SymbolId};
use std::collections::{HashMap, HashSet};

/// The complete input consumed by P9. Platform binding metadata is kept beside
Expand All @@ -23,11 +23,12 @@ pub struct ExternalBindings {
impl ExternalBindings {
/// Extracts platform binding metadata while crossing the Core boundary.
/// CC receives only this side table and therefore never needs to inspect
/// WIT names or HIR external kinds. Each declaration's resolved source type
/// is interned into the Core type table and recorded as a `type_id`, so CC
/// derives its layout from that identity instead of re-deriving it.
pub(crate) fn from_core(module: &mut CoreModule) -> Self {
let declared: Vec<(SymbolId, String, String, Option<HirType>)> = module
/// WIT names or HIR external kinds. Each declaration's checked,
/// synonym-expanded source scheme is read from Core's external signature
/// table and recorded as a `type_id`, so CC derives its layout from that
/// identity instead of re-deriving it from raw HIR.
pub(crate) fn from_core(module: &CoreModule) -> Self {
let imports = module
.externals
.iter()
.filter_map(|external| {
Expand All @@ -38,31 +39,24 @@ impl ExternalBindings {
else {
return None;
};
Some((
external.symbol,
interface.clone(),
function.clone(),
external.signature.clone(),
))
let checked = module
.external_types
.iter()
.find(|checked| checked.symbol == external.symbol);
Some(ExternalBinding {
symbol: external.symbol,
source_module: checked.map_or(module.id, |checked| checked.source_module),
interface: interface.clone(),
function: function.clone(),
type_id: checked.map(|checked| checked.ty),
span: external
.signature
.as_ref()
.map(|signature| signature.span)
.unwrap_or(module.span),
})
})
.collect();
let module_span = module.span;
let mut imports = Vec::with_capacity(declared.len());
for (symbol, interface, function, signature) in declared {
let span = signature
.as_ref()
.map_or(module_span, |signature| signature.span);
let type_id = signature
.as_ref()
.and_then(|signature| crate::abi::intern_source_type(module, signature));
imports.push(ExternalBinding {
symbol,
interface,
function,
type_id,
span,
});
}
Self { imports }
}

Expand All @@ -84,7 +78,7 @@ impl ExternalBindings {
.map_err(|message| {
vec![
BackendError::new("P8 WIT linking", binding.span, message)
.with_module(binding.symbol.module),
.with_module(binding.source_module),
]
})?;
if let Some(reason) = &import.unsupported {
Expand All @@ -94,7 +88,7 @@ impl ExternalBindings {
binding.span,
format!("WIT import `{qualified}` is unsupported: {reason}"),
)
.with_module(binding.symbol.module),
.with_module(binding.source_module),
]);
}
let Some(type_id) = binding.type_id else {
Expand All @@ -104,14 +98,14 @@ impl ExternalBindings {
binding.span,
format!("WIT import `{qualified}` has no resolved source type"),
)
.with_module(binding.symbol.module),
.with_module(binding.source_module),
]);
};
crate::abi::link::validate_import_signature(&import, module, type_id).map_err(
|message| {
vec![
BackendError::new("P8 WIT linking", binding.span, message)
.with_module(binding.symbol.module),
.with_module(binding.source_module),
]
},
)?;
Expand Down Expand Up @@ -225,6 +219,7 @@ impl ExternalBindings {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExternalBinding {
pub symbol: SymbolId,
pub source_module: ModuleId,
pub interface: String,
pub function: String,
/// The declaration's resolved source type, interned in the module type
Expand Down
2 changes: 2 additions & 0 deletions crates/psrs-backend/src/bindings/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fn wit_external(symbol: SymbolId) -> ExternalSymbol {
fn binding(symbol: SymbolId) -> ExternalBinding {
ExternalBinding {
symbol,
source_module: ModuleId(0),
interface: "wasi:cli/stdout@0.2.12".into(),
function: "log".into(),
type_id: None,
Expand All @@ -39,6 +40,7 @@ fn module(externals: Vec<ExternalSymbol>) -> CoreModule {
id: ModuleId(1),
name: "Main".into(),
externals,
external_types: Vec::new(),
types: Vec::new(),
newtype_ids: Vec::new(),
opaque_ids: Vec::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/psrs-backend/src/cc/case/coverage/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fn module(types: Vec<Type>, constructors: Vec<psrs_core::ConstructorInfo>) -> Mo
id: ModuleId(0),
name: "CoverageTest".to_owned(),
externals: Vec::new(),
external_types: Vec::new(),
types,
newtype_ids: Vec::new(),
opaque_ids: Vec::new(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fn module() -> Module {
id: ModuleId(0),
name: "RecordOracleTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Constructor(TypeConstructor::User(u)),
Type::Constructor(psrs_core::TypeConstructor::Int),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fn module() -> Module {
id: ModuleId(0),
name: "OracleTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Constructor(TypeConstructor::User(t)),
Type::Constructor(TypeConstructor::User(u)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fn bool_module() -> (Module, SymbolId, SymbolId) {
id: ModuleId(0),
name: "DecisionTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![Type::Constructor(TypeConstructor::User(type_id))],
newtype_ids: Vec::new(),
opaque_ids: Vec::new(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fn compiled_root_switch_resolves_the_realizer_root_slot() {
id: module_id,
name: "DecisionRealizeTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![Type::Constructor(TypeConstructor::User(type_id))],
newtype_ids: Vec::new(),
opaque_ids: Vec::new(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn nested_sum_patterns_project_once_per_selected_constructor_and_trap_missing_ta
id: module_id,
name: "NestedDecisionTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Constructor(TypeConstructor::User(outer_type)),
Type::Constructor(TypeConstructor::User(bool_type)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fn newtype_constructor_erases_before_nested_enum_dispatch() {
id: module_id,
name: "NewtypeDecisionTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Constructor(TypeConstructor::User(wrapper_type)),
Type::Constructor(TypeConstructor::User(bool_type)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ fn parameterized_array_field_projection_uses_its_stored_canonical_array() {
id: module_id,
name: "ParameterizedArrayDecisionTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Constructor(TypeConstructor::User(wrap_type)),
Type::Variable(TypeVariableId(0)),
Expand Down Expand Up @@ -172,6 +173,7 @@ fn nested_parameterized_projection_keeps_each_canonical_field() {
id: module_id,
name: "NestedParameterizedDecisionTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Constructor(TypeConstructor::User(inner_type)),
Type::Constructor(TypeConstructor::User(outer_type)),
Expand Down Expand Up @@ -320,6 +322,7 @@ fn generic_record_pattern_projects_its_canonical_array_field() {
id: module_id,
name: "GenericRecordDecisionTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Variable(TypeVariableId(0)),
Type::Constructor(TypeConstructor::Array),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fn single_constructor_product_dispatch_projects_and_binds_first_row_once() {
id: module_id,
name: "ProductDecisionTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Constructor(TypeConstructor::User(type_id)),
Type::Constructor(psrs_core::TypeConstructor::Int),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ fn nested_record_patterns_share_one_product_projection_and_keep_source_spans() {
id: module_id,
name: "RecordDecisionTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Constructor(TypeConstructor::User(bool_type)),
Type::Constructor(psrs_core::TypeConstructor::Int),
Expand Down
4 changes: 4 additions & 0 deletions crates/psrs-backend/src/cc/layout/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fn empty_module(types: Vec<Type>) -> Module {
id: ModuleId(0),
name: "LayoutKeysTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types,
newtype_ids: Vec::new(),
opaque_ids: Vec::new(),
Expand Down Expand Up @@ -170,6 +171,7 @@ fn equal_normalized_function_signatures_share_one_signature_id() {
id: ModuleId(0),
name: "SignatureInterningTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types,
newtype_ids: Vec::new(),
opaque_ids: Vec::new(),
Expand Down Expand Up @@ -287,6 +289,7 @@ fn integer_capture_module(capture: Type) -> Module {
id: ModuleId(0),
name: "IntegerCaptureTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![capture, Type::Constructor(psrs_core::TypeConstructor::Int)],
newtype_ids: Vec::new(),
opaque_ids: Vec::new(),
Expand Down Expand Up @@ -371,6 +374,7 @@ fn an_opaque_handle_and_an_array_of_handles_have_scalar_layouts() {
id: module_id,
name: "OpaqueHandleLayoutTest".into(),
externals: Vec::new(),
external_types: Vec::new(),
types: vec![
Type::Constructor(TypeConstructor::User(opaque)),
Type::Constructor(TypeConstructor::Array),
Expand Down
Loading
Loading