From 12c190be0bcd13ba35a340c2519651d0ae2208c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 17 Aug 2026 20:36:04 +0200 Subject: [PATCH] feat(native): expose u8 and byte profile types --- changelog.d/8330-native-u8-profile.md | 6 ++++ crates/perry-api-manifest/src/emit.rs | 4 +++ .../perry-api-manifest/src/entries/part_1.rs | 8 +++++ crates/perry-api-manifest/src/lib.rs | 3 +- .../perry-codegen/src/expr/i32_fast_path.rs | 29 +++++++++++++++++++ crates/perry-codegen/src/expr/pod_record.rs | 2 ++ .../lower_call/native_table/native_profile.rs | 9 ++++++ crates/perry-codegen/src/native_value/pod.rs | 11 ++++++- crates/perry-codegen/src/native_value/rep.rs | 2 ++ .../runtime_decls/stdlib_ffi/third_party.rs | 1 + crates/perry-hir/src/lower/context.rs | 1 + .../expr_call/intrinsics/native_scalars.rs | 2 +- crates/perry-hir/src/lower_types/extract.rs | 3 +- crates/perry-hir/tests/native_arena.rs | 12 ++++++-- .../perry-runtime/src/native_value_profile.rs | 8 +++++ crates/perry-runtime/src/value/nanbox.rs | 2 ++ docs/api/perry.d.ts | 5 +++- docs/src/api/reference.md | 3 +- docs/src/language/native-values.md | 10 +++++-- tests/fixtures/native_value_profile.ts | 22 +++++++++++++- tests/test_native_value_profile.sh | 2 +- types/perry/native/index.d.ts | 9 ++++++ 22 files changed, 141 insertions(+), 13 deletions(-) create mode 100644 changelog.d/8330-native-u8-profile.md diff --git a/changelog.d/8330-native-u8-profile.md b/changelog.d/8330-native-u8-profile.md new file mode 100644 index 0000000000..890260e3f0 --- /dev/null +++ b/changelog.d/8330-native-u8-profile.md @@ -0,0 +1,6 @@ +### Native values: expose `u8` and `byte` in `perry/native` (#6827) + +Applications can now use checked `u8(value)` conversions and exact one-byte +`u8` or `byte` fields in verifier-backed `pod` records. Out-of-range, +fractional, negative, and non-number conversions fail instead of truncating or +wrapping. diff --git a/crates/perry-api-manifest/src/emit.rs b/crates/perry-api-manifest/src/emit.rs index 0f80f009ff..9acbb433a5 100644 --- a/crates/perry-api-manifest/src/emit.rs +++ b/crates/perry-api-manifest/src/emit.rs @@ -338,6 +338,10 @@ fn entry_visible_in_dts(entry: &ApiEntry) -> bool { } fn emit_native_memory_globals(out: &mut String) { + let _ = writeln!( + out, + "type PerryU8 = number & {{ readonly __perryU8?: never }};" + ); let _ = writeln!( out, "type PerryU32 = number & {{ readonly __perryU32?: never }};" diff --git a/crates/perry-api-manifest/src/entries/part_1.rs b/crates/perry-api-manifest/src/entries/part_1.rs index 9ec04b2be5..d9517dba20 100644 --- a/crates/perry-api-manifest/src/entries/part_1.rs +++ b/crates/perry-api-manifest/src/entries/part_1.rs @@ -1515,6 +1515,14 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[ &[p_str("field")], TypeSpec::Number, )), + method_sig( + "perry/native", + "u8", + false, + None, + &[p_num("value")], + TypeSpec::Number, + ), method_sig( "perry/native", "i32", diff --git a/crates/perry-api-manifest/src/lib.rs b/crates/perry-api-manifest/src/lib.rs index 919c93e974..5d9230b563 100644 --- a/crates/perry-api-manifest/src/lib.rs +++ b/crates/perry-api-manifest/src/lib.rs @@ -554,6 +554,7 @@ mod tests { "sizeof", "alignof", "offsetof", + "u8", "i32", "i64", "u32", @@ -577,7 +578,7 @@ mod tests { assert_eq!(entry.returns, TypeSpec::Number, "{name}"); } - for name in ["i32", "i64", "u32", "u64", "usize", "f32", "f64"] { + for name in ["u8", "i32", "i64", "u32", "u64", "usize", "f32", "f64"] { let entry = module_has_symbol("perry/native", name) .unwrap_or_else(|| panic!("perry/native missing conversion {name}")); assert!(matches!(entry.kind, ApiKind::Method { .. })); diff --git a/crates/perry-codegen/src/expr/i32_fast_path.rs b/crates/perry-codegen/src/expr/i32_fast_path.rs index f3d8ae00a3..31507a3235 100644 --- a/crates/perry-codegen/src/expr/i32_fast_path.rs +++ b/crates/perry-codegen/src/expr/i32_fast_path.rs @@ -920,6 +920,7 @@ pub(crate) fn lower_expr_native( ExpectedNativeRep::JsValueBits => lower_expr_native_js_value_bits(ctx, e), ExpectedNativeRep::I32 => lower_expr_native_i32(ctx, e), ExpectedNativeRep::I64 => lower_expr_native_i64(ctx, e), + ExpectedNativeRep::U8 => lower_expr_native_u8(ctx, e), ExpectedNativeRep::U32 => lower_expr_native_u32(ctx, e), ExpectedNativeRep::U64 => lower_expr_native_u64(ctx, e), ExpectedNativeRep::USize => lower_expr_native_usize(ctx, e), @@ -952,6 +953,10 @@ fn u32_lowered(value: String) -> LoweredValue { LoweredValue::u32(value) } +fn u8_lowered(value: String) -> LoweredValue { + LoweredValue::u8(value) +} + fn u64_lowered(value: String) -> LoweredValue { LoweredValue::u64(value) } @@ -1676,6 +1681,30 @@ fn lower_expr_native_u32(ctx: &mut FnCtx<'_>, e: &Expr) -> Result Ok(lowered) } +fn lower_expr_native_u8(ctx: &mut FnCtx<'_>, e: &Expr) -> Result { + let value = match e { + Expr::Integer(n) if u8::try_from(*n).is_ok() => (*n as u8).to_string(), + _ => { + let value = lower_expr(ctx, e)?; + ctx.block().fptoui(DOUBLE, &value, I8) + } + }; + let lowered = u8_lowered(value); + ctx.record_lowered_value( + native_expr_kind(e), + None, + "lower_expr_native_u8", + &lowered, + None, + None, + None, + false, + false, + Vec::new(), + ); + Ok(lowered) +} + fn lower_expr_native_i64(ctx: &mut FnCtx<'_>, e: &Expr) -> Result { let value = match e { Expr::Integer(n) => n.to_string(), diff --git a/crates/perry-codegen/src/expr/pod_record.rs b/crates/perry-codegen/src/expr/pod_record.rs index 04c161ac02..2603d37d1b 100644 --- a/crates/perry-codegen/src/expr/pod_record.rs +++ b/crates/perry-codegen/src/expr/pod_record.rs @@ -447,6 +447,7 @@ fn coerce_js_double_to_native( field: &PodLayoutField, ) -> LoweredValue { let value = match field.native_rep { + NativeRep::U8 => ctx.block().fptoui(DOUBLE, value_js, I8), NativeRep::I32 => ctx.block().fptosi(DOUBLE, value_js, I32), NativeRep::I64 => ctx.block().fptosi(DOUBLE, value_js, I64), NativeRep::U32 | NativeRep::BufferLen => ctx.block().toint32(value_js), @@ -483,6 +484,7 @@ fn pod_field_write_compatibility_guard( fn pod_scalar_guard_rep_id(rep: &NativeRep) -> i32 { match rep { + NativeRep::U8 => 10, NativeRep::I32 => 1, NativeRep::I64 => 2, NativeRep::U32 => 3, diff --git a/crates/perry-codegen/src/lower_call/native_table/native_profile.rs b/crates/perry-codegen/src/lower_call/native_table/native_profile.rs index 19623f5561..edded9a7a9 100644 --- a/crates/perry-codegen/src/lower_call/native_table/native_profile.rs +++ b/crates/perry-codegen/src/lower_call/native_table/native_profile.rs @@ -1,6 +1,15 @@ use super::*; pub(super) const NATIVE_PROFILE_ROWS: &[NativeModSig] = &[ + NativeModSig { + module: "perry/native", + has_receiver: false, + method: "u8", + class_filter: None, + runtime: "js_perry_native_u8", + args: &[NA_F64], + ret: NR_F64, + }, NativeModSig { module: "perry/native", has_receiver: false, diff --git a/crates/perry-codegen/src/native_value/pod.rs b/crates/perry-codegen/src/native_value/pod.rs index 1076869c35..25fdfc7cb4 100644 --- a/crates/perry-codegen/src/native_value/pod.rs +++ b/crates/perry-codegen/src/native_value/pod.rs @@ -172,6 +172,10 @@ fn pod_init_value_roundtrips_exact(rep: &NativeRep, value: &Expr) -> bool { } match rep { + NativeRep::U8 => { + literal_i64(value).is_some_and(|n| u8::try_from(n).is_ok()) + || literal_f64(value).is_some_and(|n| uint_roundtrips_exact(n, 256.0)) + } NativeRep::I32 => { literal_i64(value).is_some_and(|n| i32::try_from(n).is_ok()) || literal_f64(value).is_some_and(|n| { @@ -225,7 +229,8 @@ fn checked_native_scalar_conversion_matches(rep: &NativeRep, value: &Expr) -> bo matches!( (rep, method.as_str()), - (NativeRep::I32, "i32") + (NativeRep::U8, "u8") + | (NativeRep::I32, "i32") | (NativeRep::I64, "i64") | (NativeRep::U32, "u32") | (NativeRep::U64, "u64") @@ -278,6 +283,7 @@ pub(crate) fn llvm_type_for_native_rep(rep: &NativeRep) -> Option<&'static str> Some(match rep { NativeRep::JsValue | NativeRep::F64 => DOUBLE, NativeRep::F32 => F32, + NativeRep::U8 => crate::types::I8, NativeRep::I64 | NativeRep::U64 | NativeRep::USize | NativeRep::HandleId => I64, NativeRep::I32 | NativeRep::U32 | NativeRep::BufferLen => I32, _ => return None, @@ -286,6 +292,7 @@ pub(crate) fn llvm_type_for_native_rep(rep: &NativeRep) -> Option<&'static str> pub(crate) fn expected_rep_for_native_rep(rep: &NativeRep) -> Option { Some(match rep { + NativeRep::U8 => ExpectedNativeRep::U8, NativeRep::I32 => ExpectedNativeRep::I32, NativeRep::I64 => ExpectedNativeRep::I64, NativeRep::U32 => ExpectedNativeRep::U32, @@ -366,6 +373,7 @@ fn layout_for_manifest_pod_with_prefix( pub(crate) fn scalar_size_align(rep: &NativeRep) -> Option<(u32, u32)> { Some(match rep { + NativeRep::U8 => (1, 1), NativeRep::I32 | NativeRep::U32 | NativeRep::F32 | NativeRep::BufferLen => (4, 4), NativeRep::I64 | NativeRep::U64 @@ -690,6 +698,7 @@ fn field_native_rep(ctx: &FnCtx<'_>, ty: &Type, depth: u8) -> Result match name.as_str() { + "PerryU8" => Ok(NativeRep::U8), "PerryU32" => Ok(NativeRep::U32), "PerryU64" => Ok(NativeRep::U64), "PerryUSize" => Ok(NativeRep::USize), diff --git a/crates/perry-codegen/src/native_value/rep.rs b/crates/perry-codegen/src/native_value/rep.rs index cc3d254ded..d9963a9313 100644 --- a/crates/perry-codegen/src/native_value/rep.rs +++ b/crates/perry-codegen/src/native_value/rep.rs @@ -123,6 +123,7 @@ pub(crate) enum ExpectedNativeRep { JsValueBits, I32, I64, + U8, U32, U64, USize, @@ -275,6 +276,7 @@ impl LoweredValue { (ExpectedNativeRep::JsValueBits, NativeRep::JsValueBits) | (ExpectedNativeRep::I32, NativeRep::I32) | (ExpectedNativeRep::I64, NativeRep::I64) + | (ExpectedNativeRep::U8, NativeRep::U8) | (ExpectedNativeRep::U32, NativeRep::U32) | (ExpectedNativeRep::U64, NativeRep::U64) | (ExpectedNativeRep::USize, NativeRep::USize) diff --git a/crates/perry-codegen/src/runtime_decls/stdlib_ffi/third_party.rs b/crates/perry-codegen/src/runtime_decls/stdlib_ffi/third_party.rs index 515ee47b0f..451d57acc3 100644 --- a/crates/perry-codegen/src/runtime_decls/stdlib_ffi/third_party.rs +++ b/crates/perry-codegen/src/runtime_decls/stdlib_ffi/third_party.rs @@ -50,6 +50,7 @@ pub(crate) fn declare_third_party(module: &mut LlModule) { module.declare_function("js_thread_spawn", DOUBLE, &[DOUBLE]); // `perry/native` checked scalar conversions. Results remain ordinary // JavaScript-compatible numbers at the public boundary. + module.declare_function("js_perry_native_u8", DOUBLE, &[DOUBLE]); module.declare_function("js_perry_native_i32", DOUBLE, &[DOUBLE]); module.declare_function("js_perry_native_i64", DOUBLE, &[DOUBLE]); module.declare_function("js_perry_native_u32", DOUBLE, &[DOUBLE]); diff --git a/crates/perry-hir/src/lower/context.rs b/crates/perry-hir/src/lower/context.rs index 9e822f24ef..ba786cfd40 100644 --- a/crates/perry-hir/src/lower/context.rs +++ b/crates/perry-hir/src/lower/context.rs @@ -1360,6 +1360,7 @@ impl LoweringContext { imported_name: &str, ) { let canonical = match imported_name { + "u8" | "byte" => "PerryU8", "u32" => "PerryU32", "u64" => "PerryU64", "usize" => "PerryUSize", diff --git a/crates/perry-hir/src/lower/expr_call/intrinsics/native_scalars.rs b/crates/perry-hir/src/lower/expr_call/intrinsics/native_scalars.rs index 269d0385af..b4c1cd697a 100644 --- a/crates/perry-hir/src/lower/expr_call/intrinsics/native_scalars.rs +++ b/crates/perry-hir/src/lower/expr_call/intrinsics/native_scalars.rs @@ -33,7 +33,7 @@ fn native_scalar_conversion_name<'a>( (module == "perry/native" && matches!( method, - "i32" | "i64" | "u32" | "u64" | "usize" | "f32" | "f64" + "u8" | "i32" | "i64" | "u32" | "u64" | "usize" | "f32" | "f64" )) .then_some(method) } diff --git a/crates/perry-hir/src/lower_types/extract.rs b/crates/perry-hir/src/lower_types/extract.rs index 0fc28b1cf9..79a37c90dd 100644 --- a/crates/perry-hir/src/lower_types/extract.rs +++ b/crates/perry-hir/src/lower_types/extract.rs @@ -175,7 +175,8 @@ pub(crate) fn extract_ts_type_with_ctx( if matches!( name.as_str(), - "PerryU32" + "PerryU8" + | "PerryU32" | "PerryU64" | "PerryUSize" | "PerryF32" diff --git a/crates/perry-hir/tests/native_arena.rs b/crates/perry-hir/tests/native_arena.rs index 87203e6cc6..19e04c802f 100644 --- a/crates/perry-hir/tests/native_arena.rs +++ b/crates/perry-hir/tests/native_arena.rs @@ -316,6 +316,8 @@ fn perry_native_imports_reuse_canonical_pod_lowering() { let module = lower_src( r#" import { + type u8 as Octet, + type byte as Byte, type u32 as Word, type f32, type pod as NativeRecord, @@ -326,7 +328,7 @@ fn perry_native_imports_reuse_canonical_pod_lowering() { offsetof as offsetOf, } from "perry/native"; - type Packet = NativeRecord<{ tag: Word; gain: f32; }>; + type Packet = NativeRecord<{ kind: Octet; marker: Byte; tag: Word; gain: f32; }>; const packetSize = sizeOf(); const packetAlign = alignOf(); const gainOffset = offsetOf("gain"); @@ -657,13 +659,19 @@ fn native_arena_public_view_rejects_dynamic_kind() { fn native_scalar_conversion_imports_lower_as_native_module_calls() { let module = lower_src( r#" - import { u32 as word, f32 } from "perry/native"; + import { u8 as octet, u32 as word, f32 } from "perry/native"; + const kind = octet(255); const count = word(42); const ratio = f32(0.1); "#, ) .expect("native scalar conversions should lower"); + assert!(module_any(&module, |expr| matches!( + expr, + Expr::NativeMethodCall { module, method, args, .. } + if module == "perry/native" && method == "u8" && args.len() == 1 + ))); assert!(module_any(&module, |expr| matches!( expr, Expr::NativeMethodCall { module, method, args, .. } diff --git a/crates/perry-runtime/src/native_value_profile.rs b/crates/perry-runtime/src/native_value_profile.rs index 4c283cf029..c93f0d787c 100644 --- a/crates/perry-runtime/src/native_value_profile.rs +++ b/crates/perry-runtime/src/native_value_profile.rs @@ -12,6 +12,7 @@ const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_991.0; #[derive(Clone, Copy, Debug)] enum ScalarConversion { + U8, I32, I64, U32, @@ -24,6 +25,7 @@ enum ScalarConversion { impl ScalarConversion { fn name(self) -> &'static str { match self { + Self::U8 => "u8", Self::I32 => "i32", Self::I64 => "i64", Self::U32 => "u32", @@ -43,6 +45,7 @@ fn checked_number(value: f64, conversion: ScalarConversion) -> Result integer_in_range(number, 0.0, u8::MAX as f64), ScalarConversion::I32 => integer_in_range(number, i32::MIN as f64, i32::MAX as f64), ScalarConversion::I64 => integer_in_range(number, -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER), ScalarConversion::U32 => integer_in_range(number, 0.0, u32::MAX as f64), @@ -100,6 +103,7 @@ macro_rules! scalar_conversion { } scalar_conversion!(js_perry_native_i32, I32); +scalar_conversion!(js_perry_native_u8, U8); scalar_conversion!(js_perry_native_i64, I64); scalar_conversion!(js_perry_native_u32, U32); scalar_conversion!(js_perry_native_u64, U64); @@ -113,6 +117,8 @@ mod tests { #[test] fn integer_conversions_reject_fractional_out_of_range_and_imprecise_numbers() { + assert_eq!(checked_number(0.0, ScalarConversion::U8), Ok(0.0)); + assert_eq!(checked_number(255.0, ScalarConversion::U8), Ok(255.0)); assert_eq!( checked_number(-2_147_483_648.0, ScalarConversion::I32), Ok(-2_147_483_648.0) @@ -122,6 +128,8 @@ mod tests { Ok(4_294_967_295.0) ); assert!(checked_number(1.5, ScalarConversion::I32).is_err()); + assert!(checked_number(-1.0, ScalarConversion::U8).is_err()); + assert!(checked_number(256.0, ScalarConversion::U8).is_err()); assert!(checked_number(-1.0, ScalarConversion::U32).is_err()); assert!(checked_number(4_294_967_296.0, ScalarConversion::U32).is_err()); assert!(checked_number(9_007_199_254_740_992.0, ScalarConversion::U64).is_err()); diff --git a/crates/perry-runtime/src/value/nanbox.rs b/crates/perry-runtime/src/value/nanbox.rs index 551edb90a4..8e9893e479 100644 --- a/crates/perry-runtime/src/value/nanbox.rs +++ b/crates/perry-runtime/src/value/nanbox.rs @@ -16,6 +16,7 @@ const POD_REP_F64: i32 = 6; const POD_REP_F32: i32 = 7; const POD_REP_BUFFER_LEN: i32 = 8; const POD_REP_HANDLE_ID: i32 = 9; +const POD_REP_U8: i32 = 10; // FFI functions for creating NaN-boxed values from raw pointers @@ -35,6 +36,7 @@ pub extern "C" fn js_pod_scalar_write_compatible(value: f64, native_rep: i32) -> let number = js_value.as_number(); let compatible = match native_rep { + POD_REP_U8 => uint_roundtrips_exact(number, 256.0), POD_REP_I32 => int_roundtrips_exact(number, i32::MIN as f64, (i32::MAX as f64) + 1.0), POD_REP_I64 => int_roundtrips_exact(number, i64::MIN as f64, 9_223_372_036_854_775_808.0), POD_REP_U32 | POD_REP_BUFFER_LEN => uint_roundtrips_exact(number, 4_294_967_296.0), diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index 1a13e81373..7c14313584 100644 --- a/docs/api/perry.d.ts +++ b/docs/api/perry.d.ts @@ -1,7 +1,8 @@ // Auto-generated from Perry's API manifest (#465). Do not edit by hand. // Source: perry-api-manifest::API_MANIFEST -// Coverage: 2023 entries across 123 modules +// Coverage: 2024 entries across 123 modules +type PerryU8 = number & { readonly __perryU8?: never }; type PerryU32 = number & { readonly __perryU32?: never }; type PerryU64 = number & { readonly __perryU64?: never }; type PerryUSize = number & { readonly __perryUSize?: never }; @@ -2691,6 +2692,8 @@ declare module "perry/native" { /** stdlib */ export function u64(value: number): number; /** stdlib */ + export function u8(value: number): number; + /** stdlib */ export function usize(value: number): number; } diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index 959584d73c..23796be8c6 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -2,7 +2,7 @@ This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target. -Total: 2946 entries across 125 modules. +Total: 2947 entries across 125 modules. ## Modules @@ -2538,6 +2538,7 @@ Total: 2946 entries across 125 modules. - `sizeof` — module *(intrinsic)* - `u32` — module - `u64` — module +- `u8` — module - `usize` — module ### Properties diff --git a/docs/src/language/native-values.md b/docs/src/language/native-values.md index 351abec1c0..953fe91b39 100644 --- a/docs/src/language/native-values.md +++ b/docs/src/language/native-values.md @@ -7,6 +7,7 @@ correctness, `perry/native` provides an explicit, opt-in contract. ```typescript,no-test import { + u8, i32, u32, u64, @@ -19,6 +20,7 @@ import { offsetof, } from "perry/native"; +const opcode = u8(inputOpcode); const flags = u32(inputFlags); const sequence = u64(inputSequence); const gain = f32(inputGain); @@ -53,6 +55,7 @@ ABI verifier already supports: | Type | Native representation | |---|---| +| `u8`, `byte` | unsigned 8-bit integer (`byte` is a type alias) | | `i32` | signed 32-bit integer | | `i64` | signed 64-bit integer | | `u32` | unsigned 32-bit integer | @@ -75,8 +78,9 @@ after rounding; `f64` validates that its input is finite. A non-number throws a `TypeError`; an unrepresentable number throws a `RangeError`. ```typescript,no-test -import { i32, u32, u64, f32 } from "perry/native"; +import { u8, i32, u32, u64, f32 } from "perry/native"; +const opcode = u8(dynamicOpcode); const offset = i32(dynamicOffset); const count = u32(dynamicCount); const sequence = u64(dynamicSequence); @@ -88,8 +92,8 @@ supported native ABI boundaries. A matching checked conversion may initialize a POD field from a dynamic value without forcing the whole record back to an ordinary object; the conversion guard runs before the value enters the native record. They do not change the semantics of -standalone TypeScript arithmetic. Additional widths (`i8`, `i16`, `u8`, -`u16`, and `isize`) and guaranteed native lanes across general-purpose +standalone TypeScript arithmetic. Additional widths (`i8`, `i16`, `u16`, and +`isize`) and guaranteed native lanes across general-purpose collections are later parts of the native value profile. ## POD records diff --git a/tests/fixtures/native_value_profile.ts b/tests/fixtures/native_value_profile.ts index 503f3efc4e..4250e266ce 100644 --- a/tests/fixtures/native_value_profile.ts +++ b/tests/fixtures/native_value_profile.ts @@ -4,6 +4,11 @@ type Header = NativeRecord<{ gain: FloatWord; }>; +type Tiny = NativeRecord<{ + kind: Octet; + marker: Byte; +}>; + const size = sizeOf
(); const alignment = alignOf
(); const sequenceOffset = offsetOf
("sequence"); @@ -13,6 +18,10 @@ const aliasedHeaders = headers; const convertedFlags = Word(4_294_967_295); const convertedSequence = LongWord(9_007_199_254_740_991); const convertedGain = FloatWord(0.1); +const tinySize = sizeOf(); +const markerOffset = offsetOf("marker"); +const convertedOctet = Octet(255); +const tiny: Tiny = { kind: Octet(255), marker: 7 }; const convertedHeader: Header = { flags: Word(7), sequence: LongWord(42), @@ -20,6 +29,7 @@ const convertedHeader: Header = { }; let rejectedFraction = false; let rejectedType = false; +let rejectedOctet = false; try { Word(1.5); } catch { @@ -30,13 +40,20 @@ try { } catch { rejectedType = true; } +try { + Octet(256); +} catch { + rejectedOctet = true; +} // Static imports are hoisted, including aliases used above their declaration. import { + u8 as Octet, u32 as Word, u64 as LongWord, f32 as FloatWord, type pod as NativeRecord, + type byte as Byte, type PodView, NativeArena as Arena, sizeof as sizeOf, @@ -52,9 +69,12 @@ console.log( ",flags=" + convertedFlags + ",sequenceValue=" + convertedSequence + ",gainRounded=" + (convertedGain > 0.1) + + ",tiny=" + tinySize + ":" + markerOffset + ":" + convertedOctet + + ":" + tiny.kind + ":" + tiny.marker + ",header=" + convertedHeader.flags + ":" + convertedHeader.sequence + ":" + (convertedHeader.gain > 0.1) + ",rejectedFraction=" + rejectedFraction + - ",rejectedType=" + rejectedType, + ",rejectedType=" + rejectedType + + ",rejectedOctet=" + rejectedOctet, ); arena.dispose(); diff --git a/tests/test_native_value_profile.sh b/tests/test_native_value_profile.sh index 38a711ab13..2920d10bcc 100755 --- a/tests/test_native_value_profile.sh +++ b/tests/test_native_value_profile.sh @@ -30,7 +30,7 @@ cp "$SCRIPT_DIR/fixtures/native_value_profile.ts" "$TEST_TMPDIR/main.ts" cd "$TEST_TMPDIR" "$PERRY" compile main.ts --output native_value_profile --no-cache >/dev/null -EXPECTED="size=24,align=8,sequence=8,length=1,flags=4294967295,sequenceValue=9007199254740991,gainRounded=true,header=7:42:true,rejectedFraction=true,rejectedType=true" +EXPECTED="size=24,align=8,sequence=8,length=1,flags=4294967295,sequenceValue=9007199254740991,gainRounded=true,tiny=2:1:255:255:7,header=7:42:true,rejectedFraction=true,rejectedType=true,rejectedOctet=true" ACTUAL=$(./native_value_profile) if [ "$ACTUAL" != "$EXPECTED" ]; then echo "FAIL: perry/native output mismatch" diff --git a/types/perry/native/index.d.ts b/types/perry/native/index.d.ts index 155c8af918..348a6c55d1 100644 --- a/types/perry/native/index.d.ts +++ b/types/perry/native/index.d.ts @@ -3,6 +3,15 @@ // native representation pipeline; the legacy ambient `Perry*` names remain // available for compatibility. +/** Exact-width unsigned 8-bit integer when used in a native/POD contract. */ +export type u8 = number & { readonly __perryU8?: never }; + +/** Convert a non-negative finite integer to `u8`, throwing when out of range. */ +export declare function u8(value: number): u8; + +/** Byte-sized native value; an alias for `u8`. */ +export type byte = u8; + /** Exact-width signed 32-bit integer when used in a native/POD contract. */ export type i32 = number & { readonly __perryI32?: never };