Skip to content

Commit eae653b

Browse files
authored
Merge pull request #730 from wado-lang/claude/fix-filesystem-e2e-tests-4dVm0
Add Stream<T>.read() support for WASI record types
2 parents d35e3cb + 1efc4d2 commit eae653b

39 files changed

+11380
-153
lines changed

wado-compiler/lib/core/internal.wado

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,33 @@ pub fn cm_stream_read_u8(handle: i32, max: i32) -> Array<u8> {
195195
return Array::<u8>::internal_from_raw(arr, count);
196196
}
197197

198+
/// CM adapter for stream.read(max) on Stream<T> where T is a CM record.
199+
/// Allocates a linear memory buffer sized for max*element_size bytes,
200+
/// calls stream-read, handles BLOCKED, and returns [ptr, count].
201+
/// The caller must decode each record from the buffer.
202+
pub fn cm_stream_read_raw(handle: i32, max: i32, element_size: i32, element_align: i32) -> [i32, i32] {
203+
let byte_count = max * element_size;
204+
let ptr = builtin::realloc(0, 0, element_align, byte_count);
205+
let mut result = builtin::stream_read(handle, ptr, max);
206+
if result == -1 {
207+
result = wait_for_blocked(handle);
208+
}
209+
let count = result >> 4;
210+
return [ptr, count];
211+
}
212+
213+
/// Create a new empty Array<u8> with the given capacity.
214+
/// Used by synthesized stream-read binding functions for non-u8 types.
215+
/// The type parameter is monomorphized away during compilation.
216+
pub fn array_new_empty(capacity: i32) -> Array<u8> {
217+
return Array::<u8>::with_capacity(capacity);
218+
}
219+
220+
/// Free a buffer previously allocated by cm_stream_read_raw.
221+
pub fn cm_stream_read_raw_free(ptr: i32, byte_count: i32, element_align: i32) {
222+
let freed = builtin::realloc(ptr, byte_count, element_align, 0);
223+
}
224+
198225
/// CM adapter for stream.write(data) on Stream<u8>.
199226
/// Lowers Array<u8> to linear memory, calls stream-write, handles BLOCKED.
200227
pub fn cm_stream_write_u8(handle: i32, data: Array<u8>) {

wado-compiler/src/codegen/component.rs

Lines changed: 142 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::bundled::wado_bundled_libm_wasm;
1717
use crate::component_model::{CmInstanceTypeGen, CmVariantCase, WasiFunctionInfo};
1818
use crate::hashmap::{IndexMap, IndexSet};
1919
use crate::project::Project;
20-
use crate::wir::{CanonicalIntrinsic, CmFuturePayload, CmScalarType, WirModule};
20+
use crate::wir::{CanonicalIntrinsic, CmFuturePayload, CmScalarType, CmStreamPayload, WirModule};
2121
use wasm_encoder::{
2222
Alias, CanonicalOption, ComponentBuilder, ComponentExportKind, ComponentOuterAliasKind,
2323
ComponentValType, ExportKind, InstanceType, ModuleArg, PrimitiveValType, TypeBounds,
@@ -32,14 +32,6 @@ pub fn build_component(project: &Project, core_module: &[u8], wir_module: &WirMo
3232
// Generate WASI imports dynamically from registry
3333
generate_wasi_imports(&mut builder, &mut ctx, project);
3434

35-
// Type: stream<u8> for stream intrinsics
36-
let stream_u8_type = ctx.register_type("stream-u8");
37-
{
38-
let (_, enc) = builder.ty(Some("stream-u8"));
39-
enc.defined_type()
40-
.stream(Some(ComponentValType::Primitive(PrimitiveValType::U8)));
41-
}
42-
4335
// Type: result unit for run function (needed for task.return)
4436
let result_unit_type = ctx.register_type("result-unit");
4537
{
@@ -89,6 +81,49 @@ pub fn build_component(project: &Project, core_module: &[u8], wir_module: &WirMo
8981
let all_canonical_intrinsics: Vec<CanonicalIntrinsic> =
9082
wir_module.needed_canonicals.iter().cloned().collect();
9183

84+
// Build stream types needed by canonical intrinsics.
85+
let stream_types: IndexMap<CmStreamPayload, u32> = {
86+
let mut payloads: Vec<CmStreamPayload> = Vec::new();
87+
for intrinsic in &all_canonical_intrinsics {
88+
if let Some(p) = intrinsic.stream_payload()
89+
&& !payloads.contains(&p)
90+
{
91+
payloads.push(p);
92+
}
93+
}
94+
let mut map = IndexMap::default();
95+
for payload in payloads {
96+
let (type_key, val_type) = match &payload {
97+
CmStreamPayload::U8 => (
98+
"stream-u8".to_string(),
99+
ComponentValType::Primitive(PrimitiveValType::U8),
100+
),
101+
CmStreamPayload::Record(name) => {
102+
// Alias the record type from the WASI interface that defines it.
103+
// WASI imports are already generated (generate_wasi_imports runs first),
104+
// so we can alias the exported type from the interface instance.
105+
let val = if let Some(interface_name) = project
106+
.wasi_registry
107+
.find_interface_for_struct_cm_name(name)
108+
{
109+
let inst_idx = ctx.instance_idx(&interface_name);
110+
builder.alias_export(inst_idx, name, ComponentExportKind::Type);
111+
let aliased_idx = ctx.register_type(name);
112+
ComponentValType::Type(aliased_idx)
113+
} else {
114+
ComponentValType::Primitive(PrimitiveValType::U8)
115+
};
116+
(format!("stream-{name}"), val)
117+
}
118+
};
119+
ctx.register_type(&type_key);
120+
let (_, enc) = builder.ty(Some(&type_key));
121+
enc.defined_type().stream(Some(val_type));
122+
map.insert(payload, ctx.type_idx(&type_key));
123+
}
124+
map
125+
};
126+
92127
// HTTP response types for future<T> canonical intrinsics
93128
let needs_trailers_future = all_canonical_intrinsics
94129
.iter()
@@ -105,6 +140,19 @@ pub fn build_component(project: &Project, core_module: &[u8], wir_module: &WirMo
105140
})
106141
.collect();
107142

143+
// stream<u8> type is also needed by HTTP future types
144+
let stream_u8_type = stream_types
145+
.get(&CmStreamPayload::U8)
146+
.copied()
147+
.unwrap_or_else(|| {
148+
// If no stream<u8> intrinsics are needed, define it anyway for HTTP
149+
ctx.register_type("stream-u8");
150+
let (_, enc) = builder.ty(Some("stream-u8"));
151+
enc.defined_type()
152+
.stream(Some(ComponentValType::Primitive(PrimitiveValType::U8)));
153+
ctx.type_idx("stream-u8")
154+
});
155+
108156
let (trailers_future_type, transmission_future_types) = if needs_trailers_future {
109157
let (t, http_ft) = build_future_intrinsic_types(&mut builder, &mut ctx, stream_u8_type);
110158
let mut map: IndexMap<String, u32> = IndexMap::default();
@@ -138,7 +186,7 @@ pub fn build_component(project: &Project, core_module: &[u8], wir_module: &WirMo
138186
&mut builder,
139187
&mut ctx,
140188
&all_canonical_intrinsics,
141-
stream_u8_type,
189+
&stream_types,
142190
result_unit_type,
143191
trailers_future_type,
144192
&transmission_future_types,
@@ -342,11 +390,21 @@ fn emit_cm_val_type(
342390
) -> ComponentValType {
343391
match ty {
344392
Type::Generic(g) if g.name == "Stream" => {
345-
// Only u8 streams are supported in WASI P3
346-
instance_type
347-
.ty()
348-
.defined_type()
349-
.stream(Some(ComponentValType::Primitive(PrimitiveValType::U8)));
393+
let element = g.args.first().map(|inner| {
394+
emit_cm_val_type(
395+
inner,
396+
instance_type,
397+
local_type_idx,
398+
error_code_idx,
399+
has_local_error_code,
400+
enum_export_indices,
401+
own_resource_type_indices,
402+
shared_type_gen.as_deref_mut(),
403+
project,
404+
ctx,
405+
)
406+
});
407+
instance_type.ty().defined_type().stream(element);
350408
let idx = *local_type_idx;
351409
*local_type_idx += 1;
352410
ComponentValType::Type(idx)
@@ -443,8 +501,18 @@ fn emit_cm_val_type(
443501
ComponentValType::Type(idx)
444502
}
445503
Type::Generic(g) if g.name == "Option" && !g.args.is_empty() => {
446-
let element_val_type =
447-
type_to_cm_primitive_with_resources(&g.args[0], own_resource_type_indices);
504+
let element_val_type = emit_cm_val_type(
505+
&g.args[0],
506+
instance_type,
507+
local_type_idx,
508+
error_code_idx,
509+
has_local_error_code,
510+
enum_export_indices,
511+
own_resource_type_indices,
512+
shared_type_gen,
513+
project,
514+
ctx,
515+
);
448516
instance_type.ty().defined_type().option(element_val_type);
449517
let idx = *local_type_idx;
450518
*local_type_idx += 1;
@@ -459,6 +527,8 @@ fn emit_cm_val_type(
459527
has_local_error_code,
460528
enum_export_indices,
461529
own_resource_type_indices,
530+
shared_type_gen.as_deref_mut(),
531+
project,
462532
ctx,
463533
);
464534
instance_type.ty().defined_type().tuple(tuple_types);
@@ -475,6 +545,8 @@ fn emit_cm_val_type(
475545
has_local_error_code,
476546
enum_export_indices,
477547
own_resource_type_indices,
548+
shared_type_gen.as_deref_mut(),
549+
project,
478550
ctx,
479551
);
480552
instance_type.ty().defined_type().tuple(tuple_types);
@@ -489,6 +561,22 @@ fn emit_cm_val_type(
489561
{
490562
return ComponentValType::Type(idx);
491563
}
564+
// Complex types (e.g. WASI records like Instant) use shared type gen
565+
if let (Some(type_gen), Some(proj)) = (shared_type_gen, project) {
566+
type_gen.set_next_idx(*local_type_idx);
567+
let resource_exports: IndexMap<&str, u32> = own_resource_type_indices
568+
.iter()
569+
.map(|(k, &v)| (k.as_str(), v))
570+
.collect();
571+
let val = type_gen.ast_type_to_cm(
572+
ty,
573+
instance_type,
574+
proj.wasi_registry,
575+
&resource_exports,
576+
);
577+
*local_type_idx = type_gen.next_idx();
578+
return val;
579+
}
492580
type_to_cm_primitive_with_resources(ty, own_resource_type_indices)
493581
}
494582
}
@@ -528,6 +616,8 @@ fn build_cm_tuple_types(
528616
has_local_error_code: bool,
529617
enum_export_indices: &IndexMap<String, u32>,
530618
own_resource_type_indices: &IndexMap<String, u32>,
619+
mut shared_type_gen: Option<&mut CmInstanceTypeGen>,
620+
project: Option<&Project>,
531621
ctx: &mut ComponentModelContext,
532622
) -> Vec<ComponentValType> {
533623
elems
@@ -541,8 +631,8 @@ fn build_cm_tuple_types(
541631
has_local_error_code,
542632
enum_export_indices,
543633
own_resource_type_indices,
544-
None,
545-
None,
634+
shared_type_gen.as_deref_mut(),
635+
project,
546636
ctx,
547637
)
548638
})
@@ -864,7 +954,7 @@ fn emit_canonical_intrinsics(
864954
builder: &mut ComponentBuilder,
865955
ctx: &mut ComponentModelContext,
866956
canonical_intrinsics: &[CanonicalIntrinsic],
867-
stream_u8_type: u32,
957+
stream_types: &IndexMap<CmStreamPayload, u32>,
868958
result_unit_type: u32,
869959
trailers_future_type: u32,
870960
transmission_future_types: &IndexMap<String, u32>,
@@ -874,38 +964,41 @@ fn emit_canonical_intrinsics(
874964
ctx.register_core_func(&intrinsic.import_name());
875965

876966
match intrinsic {
877-
CanonicalIntrinsic::StreamNew => {
878-
builder.stream_new(stream_u8_type);
967+
CanonicalIntrinsic::StreamNew(payload) => {
968+
let st = stream_types[payload];
969+
builder.stream_new(st);
879970
}
880-
CanonicalIntrinsic::StreamWrite => {
971+
CanonicalIntrinsic::StreamWrite(payload) => {
972+
let st = stream_types[payload];
881973
builder.stream_write(
882-
stream_u8_type,
974+
st,
883975
[
884976
CanonicalOption::Memory(ctx.memory_idx()),
885977
CanonicalOption::Realloc(ctx.core_func_idx("realloc")),
886978
],
887979
);
888980
}
889-
CanonicalIntrinsic::StreamRead => {
981+
CanonicalIntrinsic::StreamRead(payload) => {
982+
let st = stream_types[payload];
890983
builder.stream_read(
891-
stream_u8_type,
984+
st,
892985
[
893986
CanonicalOption::Memory(ctx.memory_idx()),
894987
CanonicalOption::Realloc(ctx.core_func_idx("realloc")),
895988
],
896989
);
897990
}
898-
CanonicalIntrinsic::StreamDropWritable => {
899-
builder.stream_drop_writable(stream_u8_type);
991+
CanonicalIntrinsic::StreamDropWritable(payload) => {
992+
builder.stream_drop_writable(stream_types[payload]);
900993
}
901-
CanonicalIntrinsic::StreamDropReadable => {
902-
builder.stream_drop_readable(stream_u8_type);
994+
CanonicalIntrinsic::StreamDropReadable(payload) => {
995+
builder.stream_drop_readable(stream_types[payload]);
903996
}
904-
CanonicalIntrinsic::StreamCancelRead => {
905-
builder.stream_cancel_read(stream_u8_type, false);
997+
CanonicalIntrinsic::StreamCancelRead(payload) => {
998+
builder.stream_cancel_read(stream_types[payload], false);
906999
}
907-
CanonicalIntrinsic::StreamCancelWrite => {
908-
builder.stream_cancel_write(stream_u8_type, false);
1000+
CanonicalIntrinsic::StreamCancelWrite(payload) => {
1001+
builder.stream_cancel_write(stream_types[payload], false);
9091002
}
9101003
CanonicalIntrinsic::FutureNew(payload) => {
9111004
let ft = resolve_future_type(
@@ -1407,25 +1500,35 @@ fn generate_wasi_imports(
14071500
(wado_name.to_string(), (cm_name.to_string(), cases.to_vec()))
14081501
})
14091502
.collect();
1503+
// Shared CmInstanceTypeGen for complex types across variant payloads and functions.
1504+
// Created early so variant payload types (e.g. Instant in NewTimestamp) are cached
1505+
// and reused when the same types appear in function signatures.
1506+
let mut shared_type_gen = CmInstanceTypeGen::new(local_type_idx);
1507+
for (name, &idx) in &enum_export_indices {
1508+
shared_type_gen.register_existing(&format!("enum:{name}"), idx);
1509+
}
14101510
for variant_name in &needed_variants {
14111511
if let Some((_, cases)) = interface_variants.get(variant_name) {
14121512
// Build CM variant cases: (kebab-name, optional payload type)
14131513
let cm_cases: Vec<(&str, Option<ComponentValType>)> = cases
14141514
.iter()
14151515
.map(|c| {
14161516
let payload = c.payload.as_ref().map(|ty| {
1417-
emit_cm_val_type(
1517+
shared_type_gen.set_next_idx(local_type_idx);
1518+
let val = emit_cm_val_type(
14181519
ty,
14191520
&mut instance_type,
14201521
&mut local_type_idx,
14211522
None,
14221523
has_local_error_code,
14231524
&enum_export_indices,
14241525
&own_resource_type_indices,
1425-
None,
1426-
None,
1526+
Some(&mut shared_type_gen),
1527+
Some(project),
14271528
ctx,
1428-
)
1529+
);
1530+
local_type_idx = shared_type_gen.next_idx().max(local_type_idx);
1531+
val
14291532
});
14301533
(c.cm_name.as_str(), payload)
14311534
})
@@ -1476,17 +1579,11 @@ fn generate_wasi_imports(
14761579

14771580
let mut deferred_func_exports: Vec<(String, u32)> = Vec::new();
14781581

1479-
// Shared CmInstanceTypeGen for complex ok types across all functions.
1480-
// Reusing one instance avoids duplicate type definitions and exports.
1481-
let mut shared_type_gen = CmInstanceTypeGen::new(local_type_idx);
1482-
for (name, &idx) in &enum_export_indices {
1483-
shared_type_gen.register_existing(&format!("enum:{name}"), idx);
1484-
}
1582+
// Register flags and variant export indices into shared_type_gen
14851583
for (name, &idx) in &flags_export_indices {
14861584
shared_type_gen.register_existing(&format!("flags:{name}"), idx);
14871585
}
14881586
for (name, &idx) in &variant_export_indices {
1489-
// shared_type_gen uses CM kebab-case names for cache keys
14901587
if let Some((variant_cm_name, _)) = interface_variants.get(name) {
14911588
shared_type_gen.register_existing(&format!("variant:{variant_cm_name}"), idx);
14921589
}

0 commit comments

Comments
 (0)