blob: 32a758fce46088db67537ea04e27a4807d98dede [file] [log] [blame] [raw]
// Copyright (c) 2018, Adrian Bibby Walther
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import {LlvmIrParser} from '../lib/llvm-ir';
import {LlvmPassDumpParser} from '../lib/parsers/llvm-pass-dump-parser';
import * as properties from '../lib/properties';
import {chai} from './utils';
const expect = chai.expect;
const languages = {
'c++': {id: 'c++'},
};
describe('llvm-ir parseMetaNode', function () {
let llvmIrParser;
let compilerProps;
before(() => {
let fakeProps = new properties.CompilerProps(languages, properties.fakeProps({}));
compilerProps = fakeProps.get.bind(fakeProps, 'c++');
llvmIrParser = new LlvmIrParser(compilerProps);
});
it('should parse DILocation node', function () {
llvmIrParser.parseMetaNode('!60 = !DILocation(line: 9, column: 15, scope: !58)').should.deep.equal({
metaType: 'Location',
metaId: '!60',
line: '9',
column: '15',
scope: '!58',
});
});
it('should parse distinct DILexicalBlock', function () {
llvmIrParser
.parseMetaNode('!50 = distinct !DILexicalBlock(scope: !44, file: !1, line: 8, column: 5)')
.should.deep.equal({
metaType: 'LexicalBlock',
metaId: '!50',
scope: '!44',
file: '!1',
line: '8',
column: '5',
});
});
it('should parse all value types', function () {
llvmIrParser
.parseMetaNode(
'!44 = distinct !DISubprogram(name: "func<int, int>", ' +
'scope: !1, line: 7, isLocal: false, isDefinition: true, flags: ' +
'DIFlagPrototyped, ceEmpty: "", ceTest: "a:b\\"c,d")',
)
.should.deep.equal({
metaType: 'Subprogram',
metaId: '!44',
name: 'func<int, int>',
line: '7',
scope: '!1',
isLocal: 'false',
isDefinition: 'true',
flags: 'DIFlagPrototyped',
ceTest: 'a:b\\"c,d',
ceEmpty: '',
});
});
it('should parse distinct DILexicalBlock', function () {
llvmIrParser
.parseMetaNode('!1 = !DIFile(filename: "/tmp/example.cpp", directory: "/home/compiler-explorer")')
.should.deep.equal({
metaType: 'File',
metaId: '!1',
filename: '/tmp/example.cpp',
directory: '/home/compiler-explorer',
});
});
});
describe('llvm-ir getSourceLineNumber', function () {
let llvmIrParser;
let compilerProps;
before(() => {
let fakeProps = new properties.CompilerProps(languages, properties.fakeProps({}));
compilerProps = fakeProps.get.bind(fakeProps, 'c++');
llvmIrParser = new LlvmIrParser(compilerProps);
});
const debugInfo = {
'!10': {line: 10},
'!20': {line: 20, scope: '!10'},
'!11': {scope: '!10'},
'!12': {line: 0, scope: '!10'},
'!14': {},
'!15': {scope: '!14'},
'!16': {scope: '!42'},
};
it('should return a line number', function () {
expect(llvmIrParser.getSourceLineNumber(debugInfo, '!10')).to.equal(10);
expect(llvmIrParser.getSourceLineNumber(debugInfo, '!20')).to.equal(20);
});
it('should return the line number of its parent scope', function () {
expect(llvmIrParser.getSourceLineNumber(debugInfo, '!11')).to.equal(10);
expect(llvmIrParser.getSourceLineNumber(debugInfo, '!12')).to.equal(10);
});
it('should return null on non-existend node', function () {
expect(llvmIrParser.getSourceLineNumber(debugInfo, '!16')).to.equal(null);
});
it('should return null if no higher scope has a line', function () {
expect(llvmIrParser.getSourceLineNumber(debugInfo, '!14')).to.equal(null);
expect(llvmIrParser.getSourceLineNumber(debugInfo, '!15')).to.equal(null);
});
});
describe('llvm-ir getSourceColumn', function () {
let llvmIrParser;
let compilerProps;
before(() => {
let fakeProps = new properties.CompilerProps(languages, properties.fakeProps({}));
compilerProps = fakeProps.get.bind(fakeProps, 'c++');
llvmIrParser = new LlvmIrParser(compilerProps);
});
const debugInfo = {
'!10': {column: 10},
'!20': {column: 20, scope: '!10'},
'!11': {scope: '!10'},
'!12': {column: 0, scope: '!10'},
'!14': {},
'!15': {scope: '!14'},
'!16': {scope: '!42'},
};
it('should return a column number', function () {
expect(llvmIrParser.getSourceColumn(debugInfo, '!10')).to.equal(10);
expect(llvmIrParser.getSourceColumn(debugInfo, '!20')).to.equal(20);
});
it('should return the column number of its parent scope', function () {
expect(llvmIrParser.getSourceColumn(debugInfo, '!11')).to.equal(10);
expect(llvmIrParser.getSourceColumn(debugInfo, '!12')).to.equal(10);
});
it('should return null on non-existend node', function () {
expect(llvmIrParser.getSourceColumn(debugInfo, '!16')).to.equal(null);
expect(llvmIrParser.getSourceColumn(debugInfo, '!30')).to.equal(null);
});
it('should return null if no higher scope has a column', function () {
expect(llvmIrParser.getSourceColumn(debugInfo, '!14')).to.equal(null);
expect(llvmIrParser.getSourceColumn(debugInfo, '!15')).to.equal(null);
});
});
describe('llvm-ir getFileName', function () {
let llvmIrParser;
let compilerProps;
before(() => {
let fakeProps = new properties.CompilerProps(languages, properties.fakeProps({}));
compilerProps = fakeProps.get.bind(fakeProps, 'c++');
llvmIrParser = new LlvmIrParser(compilerProps);
});
const debugInfo = {
'!10': {filename: '/test.cpp'},
'!20': {filename: '/example.cpp'},
'!11': {file: '!10'},
'!21': {file: '!20'},
'!12': {scope: '!11'},
'!13': {scope: '!12'},
};
it('should return a filename', function () {
expect(llvmIrParser.getFileName(debugInfo, '!10')).to.equal('/test.cpp');
expect(llvmIrParser.getFileName(debugInfo, '!11')).to.equal('/test.cpp');
});
it('should return the filename of its parent scope', function () {
expect(llvmIrParser.getFileName(debugInfo, '!12')).to.equal('/test.cpp');
expect(llvmIrParser.getFileName(debugInfo, '!13')).to.equal('/test.cpp');
});
it('should return null on non-existend node', function () {
expect(llvmIrParser.getFileName(debugInfo, '!42')).to.equal(null);
});
it('should not return source filename', function () {
expect(llvmIrParser.getFileName(debugInfo, '!20')).to.equal(null);
expect(llvmIrParser.getFileName(debugInfo, '!21')).to.equal(null);
});
});
describe('llvm-ir isLineLlvmDirective', function () {
let llvmIrParser;
let compilerProps;
before(() => {
let fakeProps = new properties.CompilerProps(languages, properties.fakeProps({}));
compilerProps = fakeProps.get.bind(fakeProps, 'c++');
llvmIrParser = new LlvmIrParser(compilerProps);
});
const directives = [
'source_filename = "/tmp/compiler-explorer/example.cpp"',
'!llvm.dbg.cu = !{!0}',
'!2 = !{}',
'!5 = !{i32 1, !"wchar_size", i32 4}',
'!77 = !DILocalVariable(name: "x", arg: 1, scope: !76, file: !1, line: 14, type: !10)',
'!140 = distinct !DISubprogram(name: "maxArray", linkageName: "_Z9maxArr3ayPdS_", scope: !1, ' +
'file: !1, line: 28, type: !8, isLocal: false, isDefinition: true, scopeLine: 28)',
'!150 = distinct !DILexicalBlock(scope: !146, file: !1, line: 29, column: 5)',
'!156 = !DILocation(line: 30, column: 15, scope: !154)',
'!169 = distinct !{!169, !152, !170}',
];
const nonDirectives = [
' %33 = load i32, i32* %5, align 4, !dbg !167',
' %25 = getelementptr inbounds double, double* %22, i64 %24, !dbg !129',
'define void @_Z9maxAr1rayPdS_(double*, double*) #0 !dbg !76 {',
' call void @llvm.dbg.declare(metadata double** %3, metadata !12, metadata !DIExpression()), !dbg !13',
];
it('should recognize directives', function () {
for (const directive of directives) {
llvmIrParser.isLineLlvmDirective(directive).should.be.true;
}
});
it('should recognize non-directives', function () {
for (const directive of nonDirectives) {
llvmIrParser.isLineLlvmDirective(directive).should.be.false;
}
});
});
describe('llvm-pass-dump filter', function () {
let llvmPassDumpParser;
before(() => {
let fakeProps = new properties.CompilerProps(languages, properties.fakeProps({}));
let compilerProps = fakeProps.get.bind(fakeProps, 'c++');
llvmPassDumpParser = new LlvmPassDumpParser(compilerProps);
});
// prettier-ignore
const rawFuncIR = [
{text: ' # Machine code for function f(S1&, S2 const&): NoPHIs, TracksLiveness, TiedOpsRewritten'},
{text: 'define dso_local void @f(S1&, S2 const&)(%struct.S1* noundef nonnull align 8 dereferenceable(16) %s1, %struct.S2* noundef nonnull align 8 dereferenceable(16) %s2) #0 !dbg !7 {'},
{text: 'entry:'},
{text: ' %s1.addr = alloca %struct.S1*, align 8'},
{text: ' store %struct.S1* %s1, %struct.S1** %s1.addr, align 8, !tbaa !32'},
{text: ' call void @llvm.dbg.declare(metadata %struct.S1** %s1.addr, metadata !30, metadata !DIExpression()), !dbg !36'},
{text: ' call void @llvm.dbg.value(metadata %struct.S1* %s1, metadata !30, metadata !DIExpression()), !dbg !32'},
{text: ' DBG_VALUE $rdi, $noreg, !"s1", !DIExpression(), debug-location !32; example.cpp:0 line no:7'},
{text: ' store %struct.S2* %s2, %struct.S2** %s2.addr, align 8, !tbaa !32'},
{text: ' %0 = load %struct.S2*, %struct.S2** %s2.addr, align 8, !dbg !38, !tbaa !32'},
{text: ' %a = getelementptr inbounds %struct.S2, %struct.S2* %0, i32 0, i32 0, !dbg !39'},
{text: ' %1 = load i64, i64* %t, align 8, !dbg !40, !tbaa !41'},
{text: ' %2 = load %struct.S1*, %struct.S1** %s1.addr, align 8, !dbg !46, !tbaa !32'},
{text: ' store i64 %1, i64* %t2, align 8, !dbg !49, !tbaa !50'},
{text: ' %t3 = getelementptr inbounds %struct.Wrapper2, %struct.Wrapper2* %b, i32 0, i32 0, !dbg !54'},
{text: ' ret void, !dbg !61'},
];
it('should not filter out dbg metadata', function () {
const options = {filterDebugInfo: false};
// prettier-ignore
llvmPassDumpParser
.applyIrFilters(rawFuncIR, options)
.should.deep.equal(rawFuncIR);
});
it('should filter out dbg metadata too', function () {
const options = {filterDebugInfo: true};
// prettier-ignore
llvmPassDumpParser
.applyIrFilters(rawFuncIR, options)
.should.deep.equal([
{text: ' # Machine code for function f(S1&, S2 const&): NoPHIs, TracksLiveness, TiedOpsRewritten'},
{text: 'define dso_local void @f(S1&, S2 const&)(%struct.S1* noundef nonnull align 8 dereferenceable(16) %s1, %struct.S2* noundef nonnull align 8 dereferenceable(16) %s2) {'},
{text: 'entry:'},
{text: ' %s1.addr = alloca %struct.S1*, align 8'},
{text: ' store %struct.S1* %s1, %struct.S1** %s1.addr, align 8, !tbaa !32'},
{text: ' store %struct.S2* %s2, %struct.S2** %s2.addr, align 8, !tbaa !32'},
{text: ' %0 = load %struct.S2*, %struct.S2** %s2.addr, align 8, !tbaa !32'},
{text: ' %a = getelementptr inbounds %struct.S2, %struct.S2* %0, i32 0, i32 0'},
{text: ' %1 = load i64, i64* %t, align 8, !tbaa !41'},
{text: ' %2 = load %struct.S1*, %struct.S1** %s1.addr, align 8, !tbaa !32'},
{text: ' store i64 %1, i64* %t2, align 8, !tbaa !50'},
{text: ' %t3 = getelementptr inbounds %struct.Wrapper2, %struct.Wrapper2* %b, i32 0, i32 0'},
{text: ' ret void'},
]);
});
});