Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Mypyc Development Workflows

Jukka Lehtosalo edited this pageOct 24, 2022 ·1 revision

This page explains some common workflows for developing mypyc.

Testing overview

Most mypyc test cases are defined in the same format (.test) as usedfor test cases for mypy. Look at mypy developer documentation for ageneral overview of how things work. Test cases live undermypyc/test-data/, and you can run all mypyc tests viapytest -q mypyc. If you don't make changes to code undermypy/, it's notimportant to regularly run mypy tests during development.

When you create a PR, we have Continuous Integration jobs set up thatcompile mypy using mypyc and run the mypy test suite using thecompiled mypy. This will sometimes catch additional issues not caughtby the mypyc test suite. It's okay to not do this in your localdevelopment environment.

We discuss writing tests in more detail later in this document.

Inspecting Generated IR

It's often useful to look at the generated IR when debugging issues orwhen trying to understand how mypyc compiles some code. When youcompile some module by runningmypyc, mypyc will write thepretty-printed IR intobuild/ops.txt. This is the final IR thatincludes the output from exception and reference count handlinginsertion passes.

We also have tests that verify the generate IR(mypyc/test-data/irbuild-*.text).

Type-checking Mypyc

./runtests.py self type checks mypy and mypyc. This is pretty slow,however, since it's using an uncompiled mypy.

Installing a released version of mypy usingpip (which is compiled)and usingdmypy (mypy daemon) is a much, much faster way to typecheck mypyc during development.

Inspecting Generated C

It's often useful to inspect the C code genenerate by mypyc to debugissues. Mypyc stores the generated C code asbuild/__native.c.Compiled native functions have the prefixCPyDef_, while wrapperfunctions used for calling functions from interpreted Python code havetheCPyPy_ prefix.

Hints for Implementing Typical Mypyc Features

This section gives an overview of where to look for andwhat to do to implement specific kinds of mypyc features.

Testing

Our bread-and-butter testing strategy is compiling code with mypyc andrunning it. There are downsides to this (kind of slow, tests a hugenumber of components at once, insensitive to the particular details ofthe IR), but there really is no substitute for running code. You canalso write tests that test the generated IR, however.

Tests that compile and run code

Test cases that compile and run code are located inmypyc/test-data/run*.test and the test runner is inmypyc.test.test_run. The code to compile comes after[case test<name>]. The code gets saved into the filenative.py, and itgets compiled into the modulenative.

Each test case uses a non-compiled Python driver that imports thenative module and typically calls some compiled functions. Sometests also perform assertions and print messages in the driver.

If you don't provide a driver, a default driver is used. The defaultdriver just calls each module-level function that is prefixed withtest_ and reports any uncaught exceptions as failures. (Failure tobuild or a segfault also count as failures.)testStringOps inmypyc/test-data/run-strings.test is an example of a test that usesthe default driver.

You should usually use the default driver (don't includedriver.py). It's the simplest way to write most tests.

Here's an example test case that uses the default driver:

[case testConcatenateLists]def test_concat_lists() -> None:    assert [1, 2] + [5, 6] == [1, 2, 5, 6]def test_concat_empty_lists() -> None:    assert [] + [] == []

There is one test case,testConcatenateLists. It has two sub-cases,test_concat_lists andtest_concat_empty_lists. Note that you canuse the pytest -k argument to only runtestConcetanateLists, but youcan't filter tests at the sub-case level.

It's recommended to have multiple sub-cases per test case, since eachtest case has significant fixed overhead. Each test case is run in afresh Python subprocess.

Many of the existing test cases provide a custom driver by having[file driver.py], followed by the driver implementation. Here thedriver is not compiled, which is useful if you want to testinteractions between compiled and non-compiled code. However, many ofthe tests don't have a good reason to use a custom driver -- when theywere written, the default driver wasn't available.

Test cases can also have a[out] section, which specifies theexpected contents of stdout the test case should produce. New testcases should prefer assert statements to[out] sections.

IR tests

If the specifics of the generated IR of a change is important(because, for example, you want to make sure a particular optimizationis triggering), you should add amypyc.irbuild test as well. Testcases are located inmypyc/test-data/irbuild-*.test and the testdriver is inmypyc.test.test_irbuild. IR build tests do a directcomparison of the IR output, so try to make the test as targeted aspossible so as to capture only the important details. (Many of ourexisting IR build tests do not follow this advice, unfortunately!)

If you pass the--update-data flag to pytest, it will automaticallyupdate the expected output of any tests to match the actualoutput. This is very useful for changing or creating IR build tests,but make sure to carefully inspect the diff!

You may also need to add some definitions to the stubs used forbuiltins during tests (mypyc/test-data/fixtures/ir.py). We don't usefull typeshed stubs to run tests since they would seriously slow downtests.

Benchmarking

Many mypyc improvements attempt to make some operations faster. Forany such change, you should run some measurements to verify thatthere actually is a measurable performance impact.

A typical benchmark would initialize some data to be operated on, andthen measure time spent in some function. In particular, you shouldnot measure time needed to run the entire benchmark program, as thiswould include Python startup overhead and other things that aren'trelevant. In general, for microbenchmarks, you want to do as little aspossible in the timed portion. So ideally you'll just have some loopsand the code under test. Be ready to provide your benchmark in codereview so that mypyc developers can check that the benchmark is fine(writing a good benchmark is non-trivial).

You should run a benchmark at least five times, in both original andchanged versions, ignore outliers, and report the averageruntime. Actual performance of a typical desktop or laptop computer isquite variable, due to dynamic CPU clock frequency changes, backgroundprocesses, etc. If you observe a high variance in timings, you'll needto run the benchmark more times. Also try closing most applications,including web browsers.

Interleave original and changed runs. Don't run 10 runs with variant Afollowed by 10 runs with variant B, but run an A run, a B run, an Arun, etc. Otherwise you risk that the CPU frequency will be differentbetween variants. You can also try adding a delay of 5 to 20s betweenruns to avoid CPU frequency changes.

Instead of averaging over many measurements, you can try to adjustyour environment to provide more stable measurements. However, thiscan be hard to do with some hardware, including many laptops. VictorStinner has written a series of blog posts about making measurementsstable:

Adding C Helpers

If you add an operation that compiles into a lot of C code, you mayalso want to add a C helper function for the operation to make thegenerated code smaller. Here is how to do this:

  • Declare the operation inmypyc/lib-rt/CPy.h. We avoid macros, andwe generally avoid inline functions to make it easier to targetadditional backends in the future.

  • Consider adding a unit test for your C helper inmypyc/lib-rt/test_capi.cc.We useGoogle Test for writingtests in C++. The framework is included in the repository under thedirectorygoogletest/. The C unit tests are run as part of thepytest test suite (test_c_unit_test).

Adding a Specialized Primitive Operation

Mypyc speeds up operations on primitive types such aslist andintby having primitive operations specialized for specific types. Theseoperations are declared inmypyc.primitives (andmypyc/lib-rt/CPy.h). For example,mypyc.primitives.list_opscontains primitives that target list objects.

The operation definitions are data driven: you specify the kind ofoperation (such as a call tobuiltins.len or a binary addition) andthe operand types (such aslist_primitive), and what code should begenerated for the operation. Mypyc does AST matching to find the mostsuitable primitive operation automatically.

Look at the existing primitive definitions and the docstrings inmypyc.primitives.registry for examples and more information.

Adding a New Primitive Type

Some types (typically Python Python built-in types), such asint andlist, are special cased in mypyc to generate optimized operationsspecific to these types. We'll occasionally want to add additionalprimitive types.

Here are some hints about how to add support for a new primitive type(this may be incomplete):

  • Decide whether the primitive type has an "unboxed" representation (arepresentation that is not justPyObject *). For most types we'lluse a boxed representation, as it's easier to implement and moreclosely matches Python semantics.

  • Create a new instance ofRPrimitive to support the primitive typeand add it tomypyc.ir.rtypes. Make sure all the attributes areset correctly and also define<foo>_rprimitive andis_<foo>_rprimitive.

  • Updatemypyc.irbuild.mapper.Mapper.type_to_rtype().

  • If the type is not unboxed, updateemit_cast inmypyc.codegen.emit.

If the type is unboxed, there are some additional steps:

  • Updateemit_box inmypyc.codegen.emit.

  • Updateemit_unbox inmypyc.codegen.emit.

  • Updateemit_inc_ref andemit_dec_ref inmypypc.codegen.emit.If the unboxed representation does not need reference counting,these can be no-ops.

  • Updateemit_error_check inmypyc.codegen.emit.

  • Updateemit_gc_visit andemit_gc_clear inmypyc.codegen.emitif the type has an unboxed representation with pointers.

The above may be enough to allow you to declare variables with thetype, pass values around, perform runtime type checks, and use genericfallback primitive operations to perform method calls, binaryoperations, and so on. You likely also want to add some faster,specialized primitive operations for the type (see Adding aSpecialized Primitive Operation above for how to do this).

Add a test case tomypyc/test-data/run*.test to test compilation andrunning compiled code. Ideas for things to test:

  • Test using the type as an argument.

  • Test using the type as a return value.

  • Test passing a value of the type to a function both withincompiled code and from regular Python code. Also test thisfor return values.

  • Test using the type as list item type. Test both getting a list itemand setting a list item.

Supporting More Python Syntax

Mypyc supports most Python syntax, but there are still some gaps.

Support for syntactic sugar that doesn't need additional IR operationstypically only requires changes tomypyc.irbuild.

Some new syntax also needs new IR primitives to be added tomypyc.primitives. Seemypyc.primitives.registry for documentationabout how to do this.

Other Hints

  • This developer documentation is not aimed to be very complete. Muchof our documentation is in comments and docstring in the code. Ifsomething is unclear, study the code.

  • It can be useful to look through some recent PRs to get an idea ofwhat typical code changes, test cases, etc. look like.

  • Feel free to open GitHub issues with questions if you need help whencontributing, or ask questions in existing issues. Note that we onlysupport contributors. Mypyc is not (yet) an end-user product. Youcan also ask questions in our Gitter chat(https://gitter.im/mypyc-dev/community).

Undocumented Workflows

These workflows would be useful for mypyc contributors. We should addthem to mypyc developer documentation:

  • How to inspect the generated IR before some transform passes.

Clone this wiki locally


[8]ページ先頭

©2009-2025 Movatter.jp