@@ -292,3 +292,112 @@ TEST(Bytecode, EmptyFusedLocRoundtrip) {
292292
293293 module .erase ();
294294}
295+
296+ TEST (Bytecode, LocationElision) {
297+ MLIRContext context;
298+ context.allowUnregisteredDialects ();
299+ ParserConfig config (&context);
300+
301+ // Module 1: Reuses the same location "foo" everywhere.
302+ StringRef ir1 = R"mlir(
303+ module @Test {
304+ "test.op"() : () -> () loc("foo")
305+ } loc("foo")
306+ )mlir" ;
307+
308+ // Module 2: Uses unique locations everywhere.
309+ StringRef ir2 = R"mlir(
310+ module @Test {
311+ "test.op"() : () -> () loc("a")
312+ } loc("b")
313+ )mlir" ;
314+
315+ OwningOpRef<Operation *> op1 = parseSourceString (ir1, config);
316+ OwningOpRef<Operation *> op2 = parseSourceString (ir2, config);
317+ ASSERT_TRUE (op1);
318+ ASSERT_TRUE (op2);
319+
320+ // Serialize both with location elision enabled.
321+ BytecodeWriterConfig writerConfig;
322+ writerConfig.setElideLocations (true );
323+
324+ std::string bytecode1;
325+ {
326+ llvm::raw_string_ostream os (bytecode1);
327+ ASSERT_TRUE (succeeded (writeBytecodeToFile (op1.get (), os, writerConfig)));
328+ }
329+
330+ std::string bytecode2;
331+ {
332+ llvm::raw_string_ostream os (bytecode2);
333+ ASSERT_TRUE (succeeded (writeBytecodeToFile (op2.get (), os, writerConfig)));
334+ }
335+
336+ // If location elision is working correctly, both modules must produce
337+ // the EXACT same bytecode representation, because all locations (shared or
338+ // unique) will have been collapsed into a single shared UnknownLoc.
339+ EXPECT_EQ (bytecode1, bytecode2);
340+ }
341+
342+ TEST (Bytecode, LocationElisionPreservesAttributes) {
343+ MLIRContext context;
344+ context.allowUnregisteredDialects ();
345+ ParserConfig config (&context);
346+
347+ // An operation with a debug location ("elide_me") AND a semantic attribute
348+ // that is a LocationAttr ("preserve_me").
349+ StringRef ir = R"mlir(
350+ module @Test {
351+ "test.op"() {some_loc_attr = loc("preserve_me")} : () -> () loc("elide_me")
352+ } loc("elide_me")
353+ )mlir" ;
354+
355+ OwningOpRef<Operation *> op = parseSourceString (ir, config);
356+ ASSERT_TRUE (op);
357+
358+ // Serialize with location elision enabled.
359+ BytecodeWriterConfig writerConfig;
360+ writerConfig.setElideLocations (true );
361+
362+ std::string bytecode;
363+ {
364+ llvm::raw_string_ostream os (bytecode);
365+ ASSERT_TRUE (succeeded (writeBytecodeToFile (op.get (), os, writerConfig)));
366+ }
367+
368+ // Parse it back using the bytecode reader.
369+ std::unique_ptr<Block> block = std::make_unique<Block>();
370+ ASSERT_TRUE (succeeded (readBytecodeFile (
371+ llvm::MemoryBufferRef (bytecode, " string-buffer" ), block.get (), config)));
372+
373+ // Verify we got the roundtripped module.
374+ ASSERT_FALSE (block->empty ());
375+ Operation *roundTrippedModule = &block->front ();
376+ ASSERT_TRUE (roundTrippedModule);
377+
378+ // Find the inner "test.op" operation.
379+ Operation *innerOp = nullptr ;
380+ roundTrippedModule->walk ([&](Operation *op) {
381+ if (op->getName ().getStringRef () == " test.op" ) {
382+ innerOp = op;
383+ }
384+ });
385+ ASSERT_TRUE (innerOp);
386+
387+ // 1. Verify that the debug location of "test.op" WAS elided (became
388+ // UnknownLoc).
389+ EXPECT_TRUE (isa<UnknownLoc>(innerOp->getLoc ()));
390+
391+ // 2. Verify that the semantic location attribute WAS PRESERVED.
392+ Attribute semanticLocAttr = innerOp->getAttr (" some_loc_attr" );
393+ ASSERT_TRUE (semanticLocAttr);
394+ auto locAttr = dyn_cast<LocationAttr>(semanticLocAttr);
395+ ASSERT_TRUE (locAttr);
396+
397+ // It should still be loc("preserve_me"), not UnknownLoc.
398+ EXPECT_FALSE (isa<UnknownLoc>(locAttr));
399+
400+ auto nameLoc = dyn_cast<NameLoc>(locAttr);
401+ ASSERT_TRUE (nameLoc);
402+ EXPECT_EQ (nameLoc.getName ().getValue (), " preserve_me" );
403+ }
0 commit comments