- Add initial support for Numba compilation: asm, demangling, execution

Numba wraps Python functions in `Dispatcher` objects. Each dispatcher
contains zero or more compiled argument-type-indexed overloads of its
function. We import the user's code as a module, and emit the code from
all overloads of all dispatchers that the module publicly exposes.

Name mangling is odd in Numba. It uses a similar mangling syntax to C++,
but also encodes non-symbol (`r"[^a-z0-9_]"`) characters as `_%02x`.
This encoding yields valid identifier names, so it is not strictly
invertible. Here, I have hard-coded some replacements to decode some
common cases at the cost of possible clashes with ugly user-defined
names.

Screenshot captured via `make dev EXTRA_ARGS="--debug --language numba"`

![generator
example](https://github.com/user-attachments/assets/77b8e53a-3589-4e0d-9589-10c36a026b6f)


## To do

- [x] Answer questions of #5591.
- [x] Acquire a python environment.
- [ ] Automatically run the python test?

Locally, I have installed a virtual environment with python 3.12.3 with

```shell
path/to/python -m venv venv_numba
path/to/venv_numba/bin/python -m pip install numba==0.61.0 scipy>=0.16
```

and configured its use with

```properties
# compiler-explorer/etc/config/numba.local.properties
compilers=&numba
defaultCompiler=numba_0_61_0

group.numba.compilers=numba_0_61_0
group.numba.baseName=Numba

compiler.numba_0_61_0.exe=/path/to/python3.13
compiler.numba_0_61_0.semver=0.61.0
```

I run this python-side test file with

```python
path/to/venv_numba/bin/python -m unittest etc/scripts/test_numba_wrapper.py
```

---------

Co-authored-by: Mats Jun Larsen <mats@jun.codes>
This commit is contained in:
Rupert Tombs
2025-02-19 16:41:42 +00:00
committed by GitHub
parent de1a892264
commit ca1ecbb2e3
13 changed files with 661 additions and 0 deletions

6
.github/labeler.yml vendored
View File

@@ -267,6 +267,12 @@
- 'etc/config/nim.*.properties'
- 'static/modes/nim-mode.ts'
'lang-numba':
- changed-files:
- any-glob-to-any-file:
- 'lib/compilers/numba.ts'
- 'etc/config/numba.*.properties'
'lang-objc':
- changed-files:
- any-glob-to-any-file:

View File

@@ -0,0 +1,8 @@
compilers=&numba
defaultCompiler=numba_0_61_0
group.numba.compilers=numba_0_61_0
group.numba.baseName=Numba
compiler.numba_0_61_0.exe=/opt/compiler-explorer/numba/v0.61.0/bin/python3
compiler.numba_0_61_0.semver=0.61.0

View File

@@ -0,0 +1,16 @@
compilers=&numba
defaultCompiler=numba_0_61_0
group.numba.compilers=numba_0_61_0
group.numba.baseName=Numba
compiler.numba_0_61_0.exe=/path/to/python3.13
compiler.numba_0_61_0.semver=0.61.0
compilerType=numba
demangler=c++filt
interpreted=true
isSemVer=true
needsMulti=false
supportsBinary=false
supportsExecute=true

View File

@@ -0,0 +1,124 @@
# Copyright (c) 2025, Compiler Explorer Authors
# 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.
from __future__ import annotations
import argparse
import contextlib
import importlib.util
import inspect
import sys
import traceback
from typing import TYPE_CHECKING, TextIO
from numba.core.dispatcher import Dispatcher
if TYPE_CHECKING:
from collections.abc import Iterator
from types import ModuleType
def main() -> None:
"""Load a python module and write its public numba assembly to an output stream."""
parser = argparse.ArgumentParser(
description="Output compiled asm from public numba-compiled functions.",
)
parser.add_argument("--inputfile", required=True)
parser.add_argument("--outputfile") # if not provided, we default to stdout
args = parser.parse_args()
with (
_handle_exceptions(),
_open_or_stdout(args.outputfile) as writer,
):
_write_module_asm(path=args.inputfile, writer=writer)
def _write_module_asm(*, path: str, writer: TextIO) -> None:
"""Write assembly code from compiled Numba functions in the module at `path`.
- We only take code from public Numba Dispatchers in the module.
- We add comments to indicate source line numbers.
Args:
path: Target file path containing Python code to load as a module.
writer: Where we write the resulting code.
"""
module = _load_module(path=path)
dispatchers = (
value
for name, value in inspect.getmembers(module)
# Leading underscore conventionally means private in Python culture.
# Numba manages compiled functions with Dispatcher objects.
if not name.startswith("_") and isinstance(value, Dispatcher)
)
# Multiple variables can reference the same dispatcher.
# We prefer source-ordered code for stable colors.
for dispatcher in sorted(set(dispatchers), key=_line_number):
for asm in dispatcher.inspect_asm().values():
assert isinstance(asm, str) # for static type checkers
asm = _encode_line_number(asm, _line_number(dispatcher))
writer.write(asm)
def _encode_line_number(asm: str, line_number: int) -> str:
# Numba doesn't natively encode line numbers. Appending them in comments
# at line endings makes them survive through all other asm pre-processing.
return asm.replace("\n", f";{line_number}\n")
def _line_number(dispatcher: Dispatcher) -> int:
return dispatcher.py_func.__code__.co_firstlineno
def _load_module(*, path: str, name: str = "example") -> ModuleType:
spec = importlib.util.spec_from_file_location(name, path)
assert spec is not None and spec.loader is not None # for static type checkers
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
@contextlib.contextmanager
def _handle_exceptions() -> Iterator[None]:
try:
yield
except Exception as error:
# On error, we should we mimic the compiler-explorer Python wrapper's behavior
# in its traceback-hiding and exit code.
messages = traceback.format_exception_only(type(error), error)
sys.stderr.writelines(messages)
sys.exit(255)
@contextlib.contextmanager
def _open_or_stdout(maybe_path: str | None) -> Iterator[TextIO]:
if maybe_path is None:
yield sys.stdout
return
with open(maybe_path, "w", encoding="utf-8") as writer:
yield writer
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,190 @@
# Copyright (c) 2025, Compiler Explorer Authors
# 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.
from __future__ import annotations
import argparse
import inspect
import io
import os
import sys
import unittest
import unittest.mock
import numba
from numba.core.caching import tempfile
from . import numba_wrapper
class TestMain(unittest.TestCase):
def test_with_output_file(self) -> None:
source = "<source>"
with (
tempfile.NamedTemporaryFile() as output_file,
unittest.mock.patch.object(numba_wrapper, "_write_module_asm") as mock,
unittest.mock.patch.object(
argparse.ArgumentParser,
"parse_args",
return_value=argparse.Namespace(
inputfile=source,
outputfile=output_file.name,
),
),
):
numba_wrapper.main()
mock.assert_called_once()
self.assertEqual(mock.call_args.kwargs["path"], source)
self.assertEqual(mock.call_args.kwargs["writer"].name, output_file.name)
def test_without_output_file(self) -> None:
with (
unittest.mock.patch.object(numba_wrapper, "_write_module_asm") as mock,
unittest.mock.patch.object(
argparse.ArgumentParser,
"parse_args",
return_value=argparse.Namespace(inputfile="test", outputfile=None),
),
):
numba_wrapper.main()
self.assertEqual(mock.call_args.kwargs["writer"], sys.stdout)
class TestWriteModuleAsm(unittest.TestCase):
def test_asm(self) -> None:
# This test is slow, (~0.2s in local testing).
# Reducing the optimization level (NUMBA_OPT=0) made negligible difference.
# Adding the second compiled function gave only a small increase, suggesting
# that we suffer startup overhead.
source = (
"import numba\n"
"\n"
"\n"
"@numba.njit(numba.int32(numba.int32))\n"
"def square(num):\n"
" return num * num\n"
"\n"
"\n"
"square_alias = square\n"
"\n"
"@numba.njit(numba.int32(numba.int32))\n"
"def cube(num):\n"
" return num * num * num\n"
)
writer = io.StringIO()
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, "test_empty.py")
with open(path, "w") as file_:
file_.write(source)
numba_wrapper._write_module_asm(path=path, writer=writer)
asm = writer.getvalue()
self.assertIn("square", asm)
self.assertIn("cube", asm)
# Must be sorted by line number.
self.assertLess(asm.index("square"), asm.index("cube"))
# Aliasing `square` must not duplicate its code.
self.assertEqual(asm.count("square"), asm.count("cube"))
def test_no_error_on_no_dispatcher(self) -> None:
writer = io.StringIO()
numba_wrapper._write_module_asm(path=numba_wrapper.__file__, writer=writer)
self.assertEqual(writer.getvalue(), "")
class TestLineNumber(unittest.TestCase):
def test_encode_line_number(self) -> None:
source = (
" push rbp\n"
" mov rbp, rsp\n"
" mov DWORD PTR [rbp-4], edi\n"
" mov eax, DWORD PTR [rbp-4]\n"
" imul eax, eax\n"
" pop rbp\n"
" ret\n"
)
line_number = 5678
source_commented = numba_wrapper._encode_line_number(source, line_number)
source_lines = source.split("\n")
result_lines = source_commented.split("\n")
self.assertEqual(len(source_lines), len(result_lines))
for before, after in zip(source_lines, result_lines, strict=True):
if before == "":
self.assertEqual(after, "")
continue
prefix, suffix = after.split(";")
self.assertEqual(before, prefix)
self.assertEqual(line_number, int(suffix))
def test_line_number(self) -> None:
def square(x):
return x * x
_, line_number = inspect.getsourcelines(square)
self.assertEqual(numba_wrapper._line_number(numba.njit(square)), line_number)
class TestLoadModule(unittest.TestCase):
def test_simple(self) -> None:
name = "simple"
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, "test_simple.py")
with open(path, "w") as file_:
file_.write("x = 123")
module = numba_wrapper._load_module(path=file_.name, name=name)
self.assertEqual(module.__name__, name)
self.assertEqual(module.x, 123)
class TestHandleExceptions(unittest.TestCase):
def test_no_exception(self) -> None:
with numba_wrapper._handle_exceptions() as nil:
self.assertIsNone(nil)
def test_exception(self) -> None:
text = "dQw4w9WgXcQ"
stderr = io.StringIO()
with (
self.assertRaisesRegex(SystemExit, "255"),
unittest.mock.patch.object(sys, "stderr", stderr),
numba_wrapper._handle_exceptions(),
):
assert not text, text # Just to raise an exception
self.assertIn(text, stderr.getvalue())
class TestOpenOrStdout(unittest.TestCase):
def test_open(self) -> None:
text = "lea"
with tempfile.TemporaryDirectory() as directory:
filename = os.path.join(directory, "test_open.txt")
with numba_wrapper._open_or_stdout(filename) as file_:
self.assertIsNot(file_, sys.stdout)
file_.write(text)
with open(filename) as file_:
self.assertEqual(file_.read(), text)
def test_stdout(self) -> None:
with numba_wrapper._open_or_stdout(None) as file_:
self.assertIs(file_, sys.stdout)

View File

@@ -0,0 +1,6 @@
import numba
@numba.njit("int32(int32)")
def square(num):
return num * num

View File

@@ -0,0 +1,19 @@
import itertools
import numba
@numba.njit(locals={"x": numba.uint64})
def xorshift(x):
while True:
x ^= x >> 13
x ^= x << 7
x ^= x >> 17
yield x
RNG = xorshift(1)
if __name__ == "__main__":
for x in itertools.islice(RNG, 16):
print(f"{x:016x}")

View File

@@ -105,6 +105,7 @@ export {MrustcCompiler} from './mrustc.js';
export {Msp430Compiler} from './msp430.js';
export {NasmCompiler} from './nasm.js';
export {NimCompiler} from './nim.js';
export {NumbaCompiler} from './numba.js';
export {NvccCompiler} from './nvcc.js';
export {NvcppCompiler} from './nvcpp.js';
export {NvrtcCompiler} from './nvrtc.js';

96
lib/compilers/numba.ts Normal file
View File

@@ -0,0 +1,96 @@
// Copyright (c) 2025, Compiler Explorer Authors
// 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 type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
import {BaseCompiler} from '../base-compiler.js';
import {CompilationEnvironment} from '../compilation-env.js';
import {AsmParser} from '../parsers/asm-parser.js';
import {resolvePathFromAppRoot} from '../utils.js';
import {BaseParser} from './argument-parsers.js';
export class NumbaCompiler extends BaseCompiler {
private compilerWrapperPath: string;
static get key() {
return 'numba';
}
constructor(compilerInfo: PreliminaryCompilerInfo, env: CompilationEnvironment) {
super(compilerInfo, env);
this.compilerWrapperPath =
this.compilerProps('compilerWrapper', '') || resolvePathFromAppRoot('etc', 'scripts', 'numba_wrapper.py');
}
override async processAsm(result, filters, options) {
const processed = await super.processAsm(result, filters, options);
// Numba's function-end labels survive standard filtering.
if (filters.labels) {
processed.asm = processed.asm.filter(item => !item.text.startsWith('.Lfunc_end'));
}
if (!(this.asm instanceof AsmParser)) return processed;
for (const item of processed.asm) {
// We receive line numbers as comments to line ends.
const match = item.text.match(/;(\d+)$/);
if (!match) continue;
item.text = item.text.slice(0, match.index);
const inNvccCode = false;
if (this.asm.hasOpcode(item.text, inNvccCode)) item.source = {line: Number.parseInt(match[1]), file: null};
}
return processed;
}
override async postProcessAsm(result, filters?: ParseFiltersAndOutputOptions) {
result = await super.postProcessAsm(result, filters);
for (const item of result.asm) {
let line = item.text;
// Numba includes long and noisy abi tags.
line = line.replaceAll(/\[abi:\w+]/g, '');
// Numba's custom name mangling is not invertible. It escapes symbols to
// valid Python identifiers in a "_%02x" format. Because users can write
// coinciding identifiers, we cannot perfectly demangle. Python qualifies
// scoped function names with "<locals>". There is little risk from
// collisions with user-defined symbols including `_3clocals_3e`.
line = line.replaceAll('::_3clocals_3e::', '::<locals>::');
// Numba's generators have many escaped symbols in their argument listings.
line = line.replace(/::next\(\w+_20generator_28\w+\)/, decode_symbols);
item.text = line;
}
return result;
}
override optionsForFilter(filters: ParseFiltersAndOutputOptions, outputFilename: string): string[] {
return ['-I', this.compilerWrapperPath, '--outputfile', outputFilename, '--inputfile'];
}
override getArgumentParserClass() {
return BaseParser;
}
}
export function decode_symbols(text: string): string {
// Numba escapes /[^a-z0-9_]/ characters to "_%02x"-formatted strings.
return text.replaceAll(/_([\da-f]{2})/g, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16)));
}

View File

@@ -578,6 +578,17 @@ const definitions: Record<LanguageKey, LanguageDefinition> = {
previewFilter: null,
monacoDisassembly: null,
},
numba: {
name: 'Numba',
monaco: 'python',
extensions: ['.py'],
alias: [],
logoUrl: 'numba.svg',
logoUrlDark: null,
formatter: null,
previewFilter: null,
monacoDisassembly: null,
},
objc: {
name: 'Objective-C',
monaco: 'objective-c',

161
test/numba-tests.ts Normal file
View File

@@ -0,0 +1,161 @@
// Copyright (c) 2025, Compiler Explorer Authors
// 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 {beforeAll, describe, expect, it} from 'vitest';
import type {CompilationEnvironment} from '../lib/compilation-env.js';
import {BaseParser} from '../lib/compilers/argument-parsers.js';
import {NumbaCompiler, decode_symbols} from '../lib/compilers/numba.js';
import type {AsmResultSource} from '../types/asmresult/asmresult.interfaces.js';
import type {LanguageKey} from '../types/languages.interfaces.js';
import {makeCompilationEnvironment, makeFakeCompilerInfo} from './utils.js';
describe('Numba', () => {
let ce: CompilationEnvironment;
const languages = {
numba: {id: 'numba' as LanguageKey},
};
const info = {
exe: 'none',
lang: languages.numba.id,
};
const filters = {
binary: false,
execute: false,
demangle: true,
intel: true,
commentOnly: true,
directives: true,
labels: true,
optOutput: false,
libraryCode: false,
trim: true,
binaryObject: false,
debugCalls: false,
};
const options = [];
beforeAll(() => {
ce = makeCompilationEnvironment({languages});
});
it('should quack like a numba compiler', () => {
const compiler = new NumbaCompiler(makeFakeCompilerInfo(info), ce);
expect(compiler.getArgumentParserClass()).toBe(BaseParser);
expect(NumbaCompiler.key).toEqual(languages.numba.id);
});
it('should give good wrapper script arguments', () => {
const compiler = new NumbaCompiler(makeFakeCompilerInfo(info), ce);
const outputFilename = 'test.log';
const options = compiler.optionsForFilter({}, outputFilename);
expect(options[0]).toEqual('-I');
expect(options[1]).toContain('numba_wrapper.py');
const i_outputfile = options.indexOf('--outputfile');
expect(i_outputfile).not.toEqual(-1);
expect(options[i_outputfile + 1]).toEqual(outputFilename);
expect(options.at(-1)!).toEqual('--inputfile');
});
it('processing should filter and add line numbers', async () => {
const compiler = new NumbaCompiler(makeFakeCompilerInfo(info), ce);
const asm =
' .text;123\n' +
' .file "<string>";123\n' +
' .globl _ZNmangledEdd;123\n' +
' .p2align 4, 0x90;123\n' +
' .type _ZNmangledEdd,@function;123\n' +
'_ZNmangledEdd:;123\n' +
' pushq %rbx;123\n' +
' movq %rdi, %rbx;123\n' +
' movabsq $pow, %rax;123\n' +
' callq *%rax;123\n' +
' vmovsd %xmm0, (%rbx);123\n' +
' xorl %eax, %eax;123\n' +
' popq %rbx;123\n' +
' retq;123\n' +
'.Lfunc_end0:;123\n' +
' .size _ZNmangledEdd, .Lfunc_end0-_ZNmangledEdd;123\n';
const processed = await compiler.processAsm({asm}, filters, options);
expect(processed.asm.map(item => item.text)).toEqual([
'_ZNmangledEdd:',
' pushq %rbx',
' movq %rdi, %rbx',
' movabsq $pow, %rax',
' callq *%rax',
' vmovsd %xmm0, (%rbx)',
' xorl %eax, %eax',
' popq %rbx',
' retq',
]);
expect(processed.asm[0].source).toBeNull();
for (const item of processed.asm.slice(1)) {
expect(item.source).not.toBeNull();
expect(item.source).not.toBeUndefined();
const source = item.source as AsmResultSource;
expect(source.line).not.toBeNull();
expect(source.line).not.toBeUndefined();
const line = source.line as number;
expect(line).toEqual(123);
}
});
it('should demangle special strings', async () => {
const compiler = new NumbaCompiler(makeFakeCompilerInfo(info), ce);
const asm = [
// These text strings were pre-processed by the default demangler.
{
text:
'example::factory::_3clocals_3e::power[abi:v1][abi:c8tJTIcFKzyF2ILShI4CrgQElQb6HczSBAA_3d]' +
'(double, double):',
},
{
text:
'example::xorshift64::next(' +
'uint64_20generator_28func_3d_3cfunction_20xorshift64_20at_200x7fd5948c18a0_3e_2c_20args_3d' +
'_28int64_2c_29_2c_20has_finalizer_3dTrue_29):',
},
{
text: 'example::square(Array<double, 1, C, mutable, aligned>):',
},
];
const processed = await compiler.postProcessAsm({asm}, filters);
expect(processed.asm.map(item => item.text)).toEqual([
'example::factory::<locals>::power(double, double):',
'example::xorshift64::next(uint64 generator(func=<function xorshift64 at 0x7fd5948c18a0>, args=(int64,), ' +
'has_finalizer=True)):',
'example::square(Array<double, 1, C, mutable, aligned>):',
]);
});
it('should invert the encoding accurately', () => {
expect(decode_symbols('plain_name123')).toEqual('plain_name123');
expect(decode_symbols('_3clocals_3e')).toEqual('<locals>');
const all_ascii = [...Array.from({length: 128}).keys()].map(i => String.fromCodePoint(i)).join('');
const encode = (c: string) => '_' + c.codePointAt(0)!.toString(16).padStart(2, '0');
const all_ascii_encoded = all_ascii.replaceAll(/[^\d_a-z]/g, encode);
expect(decode_symbols(all_ascii_encoded)).toEqual(all_ascii);
});
});

View File

@@ -73,6 +73,7 @@ export type LanguageKey =
| 'mlir'
| 'modula2'
| 'nim'
| 'numba'
| 'ocaml'
| 'odin'
| 'objc'

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
<style type="text/css">
.st0{fill:#00A3E0;}
.st1{fill:none;}
.st2{fill:url(#SVGID_1_);}
</style>
<g>
<path class="st0" d="M333.2,310.3c0,0,136.3-100.5,81.1-165.5c-64-75.4-243.4,84.5-263,52S310.5,86.4,329.9,73.4 c2.1-1.4,2.7-4.1,2.1-7.2l20.7-2.4l-12.9-6.5l3.3-12.9l-15,11.7c-5.6-10.1-15.2-21-20.7-24.8c-19.5-12.9-55.2-9.8-107.1,16.2 S28,154.6,89.6,245.5c55.2,81.1,213.8-58.3,240.3-35.8c22.7,19.5-152.5,139.6-152.5,139.6s74.4,0,74.7,0 C239.2,381.8,177.4,476,177.4,476l224.8-162.4C378.6,310.1,333.2,310.3,333.2,310.3z M272.1,41c8-0.6,14.9,5.6,15.5,13.5 c0.6,8-5.6,14.9-13.5,15.5c-8,0.6-14.9-5.6-15.5-13.5C257.9,48.5,264.1,41.6,272.1,41z"/>
</g>
<rect x="22.5" y="22.5" class="st1" width="455" height="455"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="301.6" y1="435.7" x2="301.6" y2="435.7" gradientTransform="matrix(1 0 0 -1 0 500)">
<stop offset="0" style="stop-color:#243746"/>
<stop offset="9.525055e-02" style="stop-color:#223442"/>
<stop offset="0.1897" style="stop-color:#1C2B36"/>
<stop offset="0.2839" style="stop-color:#121B22"/>
<stop offset="0.3773" style="stop-color:#030507"/>
<stop offset="0.3961" style="stop-color:#000000"/>
</linearGradient>
<path class="st2" d="M301.6,64.3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB