@@ -1134,8 +1134,87 @@ impl<'a> AssemblyTranslator<'a> {
11341134 return ;
11351135 }
11361136 } ;
1137- let literal = match literal_operand. value ( ) {
1138- OperandValue :: Literal ( value) => value,
1137+
1138+ let ( type_id, result_id) = self
1139+ . module_builder
1140+ . bind_typed_result ( result_type, result_id) ;
1141+
1142+ // Look up the result type to determine how to encode the literal.
1143+ // The operand is a LiteralContextDependentNumber: its encoding depends
1144+ // on the result type, matching the C++ spirv-as behavior.
1145+ let float_width = self . lookup_float_type_width ( type_id) ;
1146+
1147+ let operands = match literal_operand. value ( ) {
1148+ OperandValue :: Literal ( literal) => {
1149+ // Integer text (e.g. "42", "-1"). For float types, convert the
1150+ // integer value to float then encode as IEEE 754 bit pattern.
1151+ match float_width {
1152+ Some ( 32 ) => {
1153+ let float_val = match literal {
1154+ LiteralNumber :: Unsigned ( v) => * v as f32 ,
1155+ LiteralNumber :: Signed ( v) => * v as f32 ,
1156+ } ;
1157+ vec ! [ dr:: Operand :: LiteralBit32 ( float_val. to_bits( ) ) ]
1158+ }
1159+ Some ( 64 ) => {
1160+ let float_val = match literal {
1161+ LiteralNumber :: Unsigned ( v) => * v as f64 ,
1162+ LiteralNumber :: Signed ( v) => * v as f64 ,
1163+ } ;
1164+ vec ! [ dr:: Operand :: LiteralBit64 ( float_val. to_bits( ) ) ]
1165+ }
1166+ Some ( _) => {
1167+ // 16-bit or other widths: encode as raw bits in a 32-bit word
1168+ vec ! [ dr:: Operand :: LiteralBit32 ( literal_to_u32( literal) ) ]
1169+ }
1170+ None => {
1171+ // Integer type: encode as raw integer bits.
1172+ self . module_builder
1173+ . note_integer_constant ( result_id, literal_to_u64 ( literal) ) ;
1174+ vec ! [ encode_literal_operand( literal) ]
1175+ }
1176+ }
1177+ }
1178+ OperandValue :: Word ( word) => {
1179+ // Non-integer text (e.g. "42.5", "0x1.8p+1"). Must be a float type.
1180+ let text = word. as_str ( ) ;
1181+ if float_width. is_none ( ) {
1182+ self . module_builder . emit_error (
1183+ literal_operand. span ( ) . start ( ) ,
1184+ "integer type requires an integer literal" ,
1185+ ) ;
1186+ return ;
1187+ }
1188+ match float_width {
1189+ Some ( 32 ) => match text. parse :: < f32 > ( ) {
1190+ Ok ( val) => vec ! [ dr:: Operand :: LiteralBit32 ( val. to_bits( ) ) ] ,
1191+ Err ( _) => {
1192+ self . module_builder . emit_error (
1193+ literal_operand. span ( ) . start ( ) ,
1194+ "invalid 32-bit float literal" ,
1195+ ) ;
1196+ return ;
1197+ }
1198+ } ,
1199+ Some ( 64 ) => match text. parse :: < f64 > ( ) {
1200+ Ok ( val) => vec ! [ dr:: Operand :: LiteralBit64 ( val. to_bits( ) ) ] ,
1201+ Err ( _) => {
1202+ self . module_builder . emit_error (
1203+ literal_operand. span ( ) . start ( ) ,
1204+ "invalid 64-bit float literal" ,
1205+ ) ;
1206+ return ;
1207+ }
1208+ } ,
1209+ _ => {
1210+ self . module_builder . emit_error (
1211+ literal_operand. span ( ) . start ( ) ,
1212+ "unsupported float width for literal" ,
1213+ ) ;
1214+ return ;
1215+ }
1216+ }
1217+ }
11391218 _ => {
11401219 self . module_builder . emit_error (
11411220 literal_operand. span ( ) . start ( ) ,
@@ -1145,21 +1224,33 @@ impl<'a> AssemblyTranslator<'a> {
11451224 }
11461225 } ;
11471226
1148- let ( type_id, result_id) = self
1149- . module_builder
1150- . bind_typed_result ( result_type, result_id) ;
11511227 let inst = dr:: Instruction :: new (
11521228 spirv:: Op :: Constant ,
11531229 Some ( type_id) ,
11541230 Some ( result_id) ,
1155- vec ! [ dr :: Operand :: LiteralBit32 ( literal_to_u32 ( literal ) ) ] ,
1231+ operands ,
11561232 ) ;
1157- self . module_builder
1158- . note_integer_constant ( result_id, literal_to_u64 ( literal) ) ;
11591233 self . builder . module_mut ( ) . types_global_values . push ( inst) ;
11601234 self . record_from_module ( |module| module. types_global_values . last ( ) . cloned ( ) ) ;
11611235 }
11621236
1237+ /// Look up whether `type_id` refers to an `OpTypeFloat` instruction
1238+ /// and return its bit width if so.
1239+ fn lookup_float_type_width ( & self , type_id : u32 ) -> Option < u32 > {
1240+ self . builder
1241+ . module_ref ( )
1242+ . types_global_values
1243+ . iter ( )
1244+ . find ( |inst| {
1245+ inst. class . opcode == spirv:: Op :: TypeFloat && inst. result_id == Some ( type_id)
1246+ } )
1247+ . and_then ( |inst| inst. operands . first ( ) )
1248+ . and_then ( |op| match op {
1249+ dr:: Operand :: LiteralBit32 ( w) => Some ( * w) ,
1250+ _ => None ,
1251+ } )
1252+ }
1253+
11631254 fn translate_type_function ( & mut self , instruction : & ParsedInstruction < ' a > ) {
11641255 let opcode_pos = instruction. opcode_position ( ) ;
11651256 let Some ( result_id) = instruction. result_id ( ) else {
@@ -5913,4 +6004,245 @@ OpFunctionEnd"#;
59136004 assert ! ( convert. result_id. is_some( ) ) ;
59146005 assert_eq ! ( convert. operands. len( ) , 1 ) ;
59156006 }
6007+
6008+ // ---------------------------------------------------------------
6009+ // Context-dependent number literal tests (OpConstant / OpSpecConstant)
6010+ // Matching C++ spirv-as: integer text for float types is parsed as
6011+ // the float value, not raw bits.
6012+ // ---------------------------------------------------------------
6013+
6014+ /// Helper: assemble text, find the first OpConstant, return its operand.
6015+ fn assemble_and_get_constant_operand ( lines : & [ & str ] ) -> dr:: Operand {
6016+ let parsed: Vec < _ > = lines
6017+ . iter ( )
6018+ . map ( |line| parse_instruction ( line) . expect ( "parse" ) )
6019+ . collect ( ) ;
6020+ let refs: Vec < _ > = parsed. iter ( ) . collect ( ) ;
6021+ let module = assemble_instructions ( & refs) . expect ( "assemble" ) ;
6022+ module
6023+ . types_global_values
6024+ . iter ( )
6025+ . find ( |inst| inst. class . opcode == spirv:: Op :: Constant )
6026+ . expect ( "OpConstant not found" )
6027+ . operands
6028+ . first ( )
6029+ . expect ( "OpConstant missing operand" )
6030+ . clone ( )
6031+ }
6032+
6033+ #[ test]
6034+ fn constant_integer_literal_for_float32_encodes_as_float_value ( ) {
6035+ // "42" with float32 type should encode as float 42.0, not raw bits 0x2A.
6036+ let operand = assemble_and_get_constant_operand ( & [
6037+ "%float = OpTypeFloat 32" ,
6038+ "%c = OpConstant %float 42" ,
6039+ ] ) ;
6040+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( 42.0_f32 . to_bits( ) ) ) ;
6041+ }
6042+
6043+ #[ test]
6044+ fn constant_zero_for_float32_encodes_correctly ( ) {
6045+ let operand = assemble_and_get_constant_operand ( & [
6046+ "%float = OpTypeFloat 32" ,
6047+ "%c = OpConstant %float 0" ,
6048+ ] ) ;
6049+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( 0.0_f32 . to_bits( ) ) ) ;
6050+ }
6051+
6052+ #[ test]
6053+ fn constant_one_for_float32_encodes_correctly ( ) {
6054+ let operand = assemble_and_get_constant_operand ( & [
6055+ "%float = OpTypeFloat 32" ,
6056+ "%c = OpConstant %float 1" ,
6057+ ] ) ;
6058+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( 1.0_f32 . to_bits( ) ) ) ;
6059+ }
6060+
6061+ #[ test]
6062+ fn constant_negative_integer_for_float32_encodes_as_negative_float ( ) {
6063+ // "-1" with float32 type should encode as -1.0f (0xBF800000).
6064+ let operand = assemble_and_get_constant_operand ( & [
6065+ "%float = OpTypeFloat 32" ,
6066+ "%c = OpConstant %float -1" ,
6067+ ] ) ;
6068+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( ( -1.0_f32 ) . to_bits( ) ) ) ;
6069+ }
6070+
6071+ #[ test]
6072+ fn constant_large_integer_for_float32_encodes_as_float ( ) {
6073+ let operand = assemble_and_get_constant_operand ( & [
6074+ "%float = OpTypeFloat 32" ,
6075+ "%c = OpConstant %float 1000000" ,
6076+ ] ) ;
6077+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( 1_000_000.0_f32 . to_bits( ) ) ) ;
6078+ }
6079+
6080+ #[ test]
6081+ fn constant_integer_literal_for_float64_encodes_as_double_value ( ) {
6082+ // "42" with float64 type should encode as double 42.0.
6083+ let operand = assemble_and_get_constant_operand ( & [
6084+ "%double = OpTypeFloat 64" ,
6085+ "%c = OpConstant %double 42" ,
6086+ ] ) ;
6087+ assert_eq ! ( operand, dr:: Operand :: LiteralBit64 ( 42.0_f64 . to_bits( ) ) ) ;
6088+ }
6089+
6090+ #[ test]
6091+ fn constant_negative_integer_for_float64_encodes_as_negative_double ( ) {
6092+ let operand = assemble_and_get_constant_operand ( & [
6093+ "%double = OpTypeFloat 64" ,
6094+ "%c = OpConstant %double -1" ,
6095+ ] ) ;
6096+ assert_eq ! ( operand, dr:: Operand :: LiteralBit64 ( ( -1.0_f64 ) . to_bits( ) ) ) ;
6097+ }
6098+
6099+ #[ test]
6100+ fn constant_float_text_for_float32_encodes_correctly ( ) {
6101+ // "42.5" is float text, parsed via OperandValue::Word path.
6102+ let operand = assemble_and_get_constant_operand ( & [
6103+ "%float = OpTypeFloat 32" ,
6104+ "%c = OpConstant %float 42.5" ,
6105+ ] ) ;
6106+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( 42.5_f32 . to_bits( ) ) ) ;
6107+ }
6108+
6109+ #[ test]
6110+ fn constant_negative_float_text_for_float32_encodes_correctly ( ) {
6111+ let operand = assemble_and_get_constant_operand ( & [
6112+ "%float = OpTypeFloat 32" ,
6113+ "%c = OpConstant %float -3.14" ,
6114+ ] ) ;
6115+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( ( -3.14_f32 ) . to_bits( ) ) ) ;
6116+ }
6117+
6118+ #[ test]
6119+ fn constant_float_text_for_float64_encodes_correctly ( ) {
6120+ let operand = assemble_and_get_constant_operand ( & [
6121+ "%double = OpTypeFloat 64" ,
6122+ "%c = OpConstant %double 42.5" ,
6123+ ] ) ;
6124+ assert_eq ! ( operand, dr:: Operand :: LiteralBit64 ( 42.5_f64 . to_bits( ) ) ) ;
6125+ }
6126+
6127+ #[ test]
6128+ fn constant_integer_for_uint32_encodes_as_raw_bits ( ) {
6129+ // Integer types should still encode as raw integer bits.
6130+ let operand = assemble_and_get_constant_operand ( & [
6131+ "%uint = OpTypeInt 32 0" ,
6132+ "%c = OpConstant %uint 42" ,
6133+ ] ) ;
6134+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( 42 ) ) ;
6135+ }
6136+
6137+ #[ test]
6138+ fn constant_integer_for_sint32_encodes_as_raw_bits ( ) {
6139+ let operand = assemble_and_get_constant_operand ( & [
6140+ "%int = OpTypeInt 32 1" ,
6141+ "%c = OpConstant %int 42" ,
6142+ ] ) ;
6143+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( 42 ) ) ;
6144+ }
6145+
6146+ #[ test]
6147+ fn constant_negative_for_sint32_encodes_twos_complement ( ) {
6148+ let operand = assemble_and_get_constant_operand ( & [
6149+ "%int = OpTypeInt 32 1" ,
6150+ "%c = OpConstant %int -1" ,
6151+ ] ) ;
6152+ // -1 as i32 in two's complement is 0xFFFFFFFF
6153+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( ( -1_i32 ) as u32 ) ) ;
6154+ }
6155+
6156+ #[ test]
6157+ fn constant_integer_for_uint64_encodes_value ( ) {
6158+ // Small values that fit in 32 bits are stored as LiteralBit32 by
6159+ // encode_literal_operand. This is a pre-existing behavior; the binary
6160+ // serializer handles type-width encoding.
6161+ let operand = assemble_and_get_constant_operand ( & [
6162+ "%ulong = OpTypeInt 64 0" ,
6163+ "%c = OpConstant %ulong 42" ,
6164+ ] ) ;
6165+ assert_eq ! ( operand, dr:: Operand :: LiteralBit32 ( 42 ) ) ;
6166+ }
6167+
6168+ #[ test]
6169+ fn constant_large_integer_for_uint64_encodes_as_64bit ( ) {
6170+ // Values that don't fit in 32 bits should use LiteralBit64.
6171+ let operand = assemble_and_get_constant_operand ( & [
6172+ "%ulong = OpTypeInt 64 0" ,
6173+ "%c = OpConstant %ulong 4294967296" ,
6174+ ] ) ;
6175+ assert_eq ! ( operand, dr:: Operand :: LiteralBit64 ( 4_294_967_296 ) ) ;
6176+ }
6177+
6178+ #[ test]
6179+ fn constant_float_round_trips_through_assemble_disassemble ( ) {
6180+ // Full round-trip: assemble "OpConstant %float 42" then disassemble
6181+ // and verify the output shows 42 (the float value), not 5.88545e-44.
6182+ let text = [
6183+ "OpCapability Shader" ,
6184+ "OpMemoryModel Logical GLSL450" ,
6185+ "%float = OpTypeFloat 32" ,
6186+ "%c = OpConstant %float 42" ,
6187+ ]
6188+ . join ( "\n " ) ;
6189+ let disassembled = round_trip_with_options (
6190+ & text,
6191+ TextToBinaryOptions :: NONE ,
6192+ BinaryToTextOptions :: NO_HEADER ,
6193+ ) ;
6194+ assert ! (
6195+ disassembled. contains( "OpConstant" ) && disassembled. contains( " 42" ) ,
6196+ "Expected disassembly to contain 'OpConstant ... 42', got: {disassembled}"
6197+ ) ;
6198+ // Must NOT contain the subnormal float that 0x2A bit pattern represents
6199+ assert ! (
6200+ !disassembled. contains( "5.88545" ) ,
6201+ "OpConstant should not show raw bits interpretation: {disassembled}"
6202+ ) ;
6203+ }
6204+
6205+ #[ test]
6206+ fn constant_float_text_round_trips_through_assemble_disassemble ( ) {
6207+ let text = [
6208+ "OpCapability Shader" ,
6209+ "OpMemoryModel Logical GLSL450" ,
6210+ "%float = OpTypeFloat 32" ,
6211+ "%c = OpConstant %float 42.5" ,
6212+ ]
6213+ . join ( "\n " ) ;
6214+ let disassembled = round_trip_with_options (
6215+ & text,
6216+ TextToBinaryOptions :: NONE ,
6217+ BinaryToTextOptions :: NO_HEADER ,
6218+ ) ;
6219+ assert ! (
6220+ disassembled. contains( "42.5" ) ,
6221+ "Expected disassembly to contain '42.5', got: {disassembled}"
6222+ ) ;
6223+ }
6224+
6225+ #[ test]
6226+ fn constant_does_not_note_integer_constant_for_float_type ( ) {
6227+ // When the type is float, note_integer_constant should NOT be called.
6228+ // Verify by checking that a subsequent array-length lookup doesn't
6229+ // confuse float bits with integer values.
6230+ let type_inst = parse_instruction ( "%float = OpTypeFloat 32" ) . unwrap ( ) ;
6231+ let const_inst = parse_instruction ( "%c = OpConstant %float 42" ) . unwrap ( ) ;
6232+ let mut translator = AssemblyTranslator :: new ( ) ;
6233+ translator. translate ( & type_inst) ;
6234+ translator. translate ( & const_inst) ;
6235+ let ( module, diagnostics) = translator. finish ( ) ;
6236+ assert ! ( diagnostics. is_empty( ) ) ;
6237+ let constant = module
6238+ . types_global_values
6239+ . iter ( )
6240+ . find ( |inst| inst. class . opcode == spirv:: Op :: Constant )
6241+ . expect ( "constant" ) ;
6242+ // The operand should be 42.0f bits, not integer 42
6243+ assert_eq ! (
6244+ constant. operands. first( ) . unwrap( ) ,
6245+ & dr:: Operand :: LiteralBit32 ( 42.0_f32 . to_bits( ) )
6246+ ) ;
6247+ }
59166248}
0 commit comments